diff options
Diffstat (limited to 'src/multimedia/platform/android')
68 files changed, 13009 insertions, 0 deletions
diff --git a/src/multimedia/platform/android/android.pri b/src/multimedia/platform/android/android.pri new file mode 100644 index 000000000..ce9fa093e --- /dev/null +++ b/src/multimedia/platform/android/android.pri @@ -0,0 +1,12 @@ +QT += opengl core-private network + +HEADERS += \ + $$PWD/qandroidmediaserviceplugin_p.h + +SOURCES += \ + $$PWD/qandroidmediaserviceplugin.cpp + +include(wrappers/jni/jni.pri) +include(common/common.pri) +include(mediaplayer/mediaplayer.pri) +include(mediacapture/mediacapture.pri) 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 diff --git a/src/multimedia/platform/android/mediacapture/mediacapture.pri b/src/multimedia/platform/android/mediacapture/mediacapture.pri new file mode 100644 index 000000000..a1f4b41a6 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/mediacapture.pri @@ -0,0 +1,39 @@ +INCLUDEPATH += $$PWD + +SOURCES += \ + $$PWD/qandroidcaptureservice.cpp \ + $$PWD/qandroidcameracontrol.cpp \ + $$PWD/qandroidvideodeviceselectorcontrol.cpp \ + $$PWD/qandroidcamerasession.cpp \ + $$PWD/qandroidcameraexposurecontrol.cpp \ + $$PWD/qandroidcameraimageprocessingcontrol.cpp \ + $$PWD/qandroidimageencodercontrol.cpp \ + $$PWD/qandroidcameraimagecapturecontrol.cpp \ + $$PWD/qandroidcamerafocuscontrol.cpp \ + $$PWD/qandroidcapturesession.cpp \ + $$PWD/qandroidmediarecordercontrol.cpp \ + $$PWD/qandroidaudioencodersettingscontrol.cpp \ + $$PWD/qandroidmediacontainercontrol.cpp \ + $$PWD/qandroidvideoencodersettingscontrol.cpp \ + $$PWD/qandroidaudioinputselectorcontrol.cpp \ + $$PWD/qandroidmediavideoprobecontrol.cpp \ + $$PWD/qandroidcameravideorenderercontrol.cpp + +HEADERS += \ + $$PWD/qandroidcaptureservice_p.h \ + $$PWD/qandroidcameracontrol_p.h \ + $$PWD/qandroidvideodeviceselectorcontrol_p.h \ + $$PWD/qandroidcamerasession_p.h \ + $$PWD/qandroidcameraexposurecontrol_p.h \ + $$PWD/qandroidcameraimageprocessingcontrol_p.h \ + $$PWD/qandroidimageencodercontrol_p.h \ + $$PWD/qandroidcameraimagecapturecontrol_p.h \ + $$PWD/qandroidcamerafocuscontrol_p.h \ + $$PWD/qandroidcapturesession_p.h \ + $$PWD/qandroidmediarecordercontrol_p.h \ + $$PWD/qandroidaudioencodersettingscontrol_p.h \ + $$PWD/qandroidmediacontainercontrol_p.h \ + $$PWD/qandroidvideoencodersettingscontrol_p.h \ + $$PWD/qandroidaudioinputselectorcontrol_p.h \ + $$PWD/qandroidmediavideoprobecontrol_p.h \ + $$PWD/qandroidcameravideorenderercontrol_p.h diff --git a/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol.cpp new file mode 100644 index 000000000..c32a93cee --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol.cpp @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** 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 "qandroidaudioencodersettingscontrol.h" + +#include "qandroidcapturesession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidAudioEncoderSettingsControl::QAndroidAudioEncoderSettingsControl(QAndroidCaptureSession *session) + : QAudioEncoderSettingsControl() + , m_session(session) +{ +} + +QStringList QAndroidAudioEncoderSettingsControl::supportedAudioCodecs() const +{ + return QStringList() << QLatin1String("amr-nb") << QLatin1String("amr-wb") << QLatin1String("aac"); +} + +QString QAndroidAudioEncoderSettingsControl::codecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("amr-nb")) + return tr("Adaptive Multi-Rate Narrowband (AMR-NB) audio codec"); + else if (codecName == QLatin1String("amr-wb")) + return tr("Adaptive Multi-Rate Wideband (AMR-WB) audio codec"); + else if (codecName == QLatin1String("aac")) + return tr("AAC Low Complexity (AAC-LC) audio codec"); + + return QString(); +} + +QList<int> QAndroidAudioEncoderSettingsControl::supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous) const +{ + if (continuous) + *continuous = false; + + if (settings.isNull() || settings.codec().isNull() || settings.codec() == QLatin1String("aac")) { + return QList<int>() << 8000 << 11025 << 12000 << 16000 << 22050 + << 24000 << 32000 << 44100 << 48000 << 96000; + } else if (settings.codec() == QLatin1String("amr-nb")) { + return QList<int>() << 8000; + } else if (settings.codec() == QLatin1String("amr-wb")) { + return QList<int>() << 16000; + } + + return QList<int>(); +} + +QAudioEncoderSettings QAndroidAudioEncoderSettingsControl::audioSettings() const +{ + return m_session->audioSettings(); +} + +void QAndroidAudioEncoderSettingsControl::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_session->setAudioSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol_p.h new file mode 100644 index 000000000..7259ff685 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidaudioencodersettingscontrol_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 QANDROIDAUDIOENCODERSETTINGSCONTROL_H +#define QANDROIDAUDIOENCODERSETTINGSCONTROL_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 <qaudioencodersettingscontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCaptureSession; + +class QAndroidAudioEncoderSettingsControl : public QAudioEncoderSettingsControl +{ + Q_OBJECT +public: + explicit QAndroidAudioEncoderSettingsControl(QAndroidCaptureSession *session); + + QStringList supportedAudioCodecs() const override; + QString codecDescription(const QString &codecName) const override; + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous = 0) const override; + QAudioEncoderSettings audioSettings() const override; + void setAudioSettings(const QAudioEncoderSettings &settings) override; + +private: + QAndroidCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDAUDIOENCODERSETTINGSCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol.cpp new file mode 100644 index 000000000..9745d0725 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol.cpp @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** 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 "qandroidaudioinputselectorcontrol.h" + +#include "qandroidcapturesession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidAudioInputSelectorControl::QAndroidAudioInputSelectorControl(QAndroidCaptureSession *session) + : QAudioInputSelectorControl() + , m_session(session) +{ + connect(m_session, SIGNAL(audioInputChanged(QString)), this, SIGNAL(activeInputChanged(QString))); +} + +QList<QString> QAndroidAudioInputSelectorControl::availableInputs() const +{ + return QList<QString>() << QLatin1String("default") + << QLatin1String("mic") + << QLatin1String("voice_uplink") + << QLatin1String("voice_downlink") + << QLatin1String("voice_call") + << QLatin1String("voice_recognition"); +} + +QString QAndroidAudioInputSelectorControl::inputDescription(const QString& name) const +{ + return availableDeviceDescription(name.toLatin1()); +} + +QString QAndroidAudioInputSelectorControl::defaultInput() const +{ + return QLatin1String("default"); +} + +QString QAndroidAudioInputSelectorControl::activeInput() const +{ + return m_session->audioInput(); +} + +void QAndroidAudioInputSelectorControl::setActiveInput(const QString& name) +{ + m_session->setAudioInput(name); +} + +QList<QByteArray> QAndroidAudioInputSelectorControl::availableDevices() +{ + return QList<QByteArray>() << "default" + << "mic" + << "voice_uplink" + << "voice_downlink" + << "voice_call" + << "voice_recognition"; +} + +QString QAndroidAudioInputSelectorControl::availableDeviceDescription(const QByteArray &device) +{ + if (device == "default") + return QLatin1String("Default audio source"); + else if (device == "mic") + return QLatin1String("Microphone audio source"); + else if (device == "voice_uplink") + return QLatin1String("Voice call uplink (Tx) audio source"); + else if (device == "voice_downlink") + return QLatin1String("Voice call downlink (Rx) audio source"); + else if (device == "voice_call") + return QLatin1String("Voice call uplink + downlink audio source"); + else if (device == "voice_recognition") + return QLatin1String("Microphone audio source tuned for voice recognition"); + else + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol_p.h new file mode 100644 index 000000000..ef53dbdae --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidaudioinputselectorcontrol_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** 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 QANDROIDAUDIOINPUTSELECTORCONTROL_H +#define QANDROIDAUDIOINPUTSELECTORCONTROL_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 <qaudioinputselectorcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCaptureSession; + +class QAndroidAudioInputSelectorControl : public QAudioInputSelectorControl +{ + Q_OBJECT +public: + explicit QAndroidAudioInputSelectorControl(QAndroidCaptureSession *session); + + QList<QString> availableInputs() const; + QString inputDescription(const QString& name) const; + QString defaultInput() const; + + QString activeInput() const; + void setActiveInput(const QString& name); + + static QList<QByteArray> availableDevices(); + static QString availableDeviceDescription(const QByteArray &device); + +private: + QAndroidCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDAUDIOINPUTSELECTORCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameracontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcameracontrol.cpp new file mode 100644 index 000000000..18f6de7c1 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameracontrol.cpp @@ -0,0 +1,343 @@ +/**************************************************************************** +** +** 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 "qandroidcameracontrol_p.h" +#include "qandroidcamerasession_p.h" +#include <qtimer.h> + +QT_BEGIN_NAMESPACE + +QAndroidCameraControl::QAndroidCameraControl(QAndroidCameraSession *session) + : QCameraControl(0) + , m_cameraSession(session) + +{ + connect(m_cameraSession, SIGNAL(statusChanged(QCamera::Status)), + this, SIGNAL(statusChanged(QCamera::Status))); + + connect(m_cameraSession, SIGNAL(stateChanged(QCamera::State)), + this, SIGNAL(stateChanged(QCamera::State))); + + connect(m_cameraSession, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString))); + + connect(m_cameraSession, SIGNAL(captureModeChanged(QCamera::CaptureModes)), + this, SIGNAL(captureModeChanged(QCamera::CaptureModes))); + + connect(m_cameraSession, SIGNAL(opened()), this, SLOT(onCameraOpened())); + + m_recalculateTimer = new QTimer(this); + m_recalculateTimer->setInterval(1000); + m_recalculateTimer->setSingleShot(true); + connect(m_recalculateTimer, SIGNAL(timeout()), this, SLOT(onRecalculateTimeOut())); +} + +QAndroidCameraControl::~QAndroidCameraControl() +{ +} + +QCamera::CaptureModes QAndroidCameraControl::captureMode() const +{ + return m_cameraSession->captureMode(); +} + +void QAndroidCameraControl::setCaptureMode(QCamera::CaptureModes mode) +{ + m_cameraSession->setCaptureMode(mode); +} + +bool QAndroidCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + return m_cameraSession->isCaptureModeSupported(mode); +} + +void QAndroidCameraControl::setState(QCamera::State state) +{ + m_cameraSession->setState(state); +} + +QCamera::State QAndroidCameraControl::state() const +{ + return m_cameraSession->state(); +} + +QCamera::Status QAndroidCameraControl::status() const +{ + return m_cameraSession->status(); +} + +bool QAndroidCameraControl::canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(status); + + switch (changeType) { + case QCameraControl::CaptureMode: + case QCameraControl::ImageEncodingSettings: + case QCameraControl::VideoEncodingSettings: + case QCameraControl::Viewfinder: + return true; + default: + return false; + } +} + + +QCamera::LockTypes QAndroidCameraControl::supportedLocks() const +{ + return m_supportedLocks; +} + +QCamera::LockStatus QAndroidCameraControl::lockStatus(QCamera::LockType lock) const +{ + if (!m_supportedLocks.testFlag(lock) || !m_cameraSession->camera()) + return QCamera::Unlocked; + + if (lock == QCamera::LockFocus) + return m_focusLockStatus; + + if (lock == QCamera::LockExposure) + return m_exposureLockStatus; + + if (lock == QCamera::LockWhiteBalance) + return m_whiteBalanceLockStatus; + + return QCamera::Unlocked; +} + +void QAndroidCameraControl::searchAndLock(QCamera::LockTypes locks) +{ + if (!m_cameraSession->camera()) + return; + + // filter out unsupported locks + locks &= m_supportedLocks; + + if (locks.testFlag(QCamera::LockFocus)) { + QString focusMode = m_cameraSession->camera()->getFocusMode(); + if (focusMode == QLatin1String("auto") + || focusMode == QLatin1String("macro") + || focusMode == QLatin1String("continuous-picture") + || focusMode == QLatin1String("continuous-video")) { + + if (m_focusLockStatus == QCamera::Searching) + m_cameraSession->camera()->cancelAutoFocus(); + else + setFocusLockStatus(QCamera::Searching, QCamera::UserRequest); + + m_cameraSession->camera()->autoFocus(); + + } else { + setFocusLockStatus(QCamera::Locked, QCamera::LockAcquired); + } + } + + if (locks.testFlag(QCamera::LockExposure) && m_exposureLockStatus != QCamera::Searching) { + if (m_cameraSession->camera()->getAutoExposureLock()) { + // if already locked, unlock and give some time to recalculate exposure + m_cameraSession->camera()->setAutoExposureLock(false); + setExposureLockStatus(QCamera::Searching, QCamera::UserRequest); + } else { + m_cameraSession->camera()->setAutoExposureLock(true); + setExposureLockStatus(QCamera::Locked, QCamera::LockAcquired); + } + } + + if (locks.testFlag(QCamera::LockWhiteBalance) && m_whiteBalanceLockStatus != QCamera::Searching) { + if (m_cameraSession->camera()->getAutoWhiteBalanceLock()) { + // if already locked, unlock and give some time to recalculate white balance + m_cameraSession->camera()->setAutoWhiteBalanceLock(false); + setWhiteBalanceLockStatus(QCamera::Searching, QCamera::UserRequest); + } else { + m_cameraSession->camera()->setAutoWhiteBalanceLock(true); + setWhiteBalanceLockStatus(QCamera::Locked, QCamera::LockAcquired); + } + } + + if (m_exposureLockStatus == QCamera::Searching || m_whiteBalanceLockStatus == QCamera::Searching) + m_recalculateTimer->start(); +} + +void QAndroidCameraControl::unlock(QCamera::LockTypes locks) +{ + if (!m_cameraSession->camera()) + return; + + if (m_recalculateTimer->isActive()) + m_recalculateTimer->stop(); + + // filter out unsupported locks + locks &= m_supportedLocks; + + if (locks.testFlag(QCamera::LockFocus)) { + m_cameraSession->camera()->cancelAutoFocus(); + setFocusLockStatus(QCamera::Unlocked, QCamera::UserRequest); + } + + if (locks.testFlag(QCamera::LockExposure)) { + m_cameraSession->camera()->setAutoExposureLock(false); + setExposureLockStatus(QCamera::Unlocked, QCamera::UserRequest); + } + + if (locks.testFlag(QCamera::LockWhiteBalance)) { + m_cameraSession->camera()->setAutoWhiteBalanceLock(false); + setWhiteBalanceLockStatus(QCamera::Unlocked, QCamera::UserRequest); + } +} + +void QAndroidCameraControl::onCameraOpened() +{ + m_supportedLocks = QCamera::NoLock; + m_focusLockStatus = QCamera::Unlocked; + m_exposureLockStatus = QCamera::Unlocked; + m_whiteBalanceLockStatus = QCamera::Unlocked; + + // check if focus lock is supported + QStringList focusModes = m_cameraSession->camera()->getSupportedFocusModes(); + for (int i = 0; i < focusModes.size(); ++i) { + const QString &focusMode = focusModes.at(i); + if (focusMode == QLatin1String("auto") + || focusMode == QLatin1String("continuous-picture") + || focusMode == QLatin1String("continuous-video") + || focusMode == QLatin1String("macro")) { + + m_supportedLocks |= QCamera::LockFocus; + setFocusLockStatus(QCamera::Unlocked, QCamera::UserRequest); + + connect(m_cameraSession->camera(), SIGNAL(autoFocusComplete(bool)), + this, SLOT(onCameraAutoFocusComplete(bool))); + + break; + } + } + + if (m_cameraSession->camera()->isAutoExposureLockSupported()) { + m_supportedLocks |= QCamera::LockExposure; + setExposureLockStatus(QCamera::Unlocked, QCamera::UserRequest); + } + + if (m_cameraSession->camera()->isAutoWhiteBalanceLockSupported()) { + m_supportedLocks |= QCamera::LockWhiteBalance; + setWhiteBalanceLockStatus(QCamera::Unlocked, QCamera::UserRequest); + + connect(m_cameraSession->camera(), SIGNAL(whiteBalanceChanged()), + this, SLOT(onWhiteBalanceChanged())); + } +} + +void QAndroidCameraControl::onCameraAutoFocusComplete(bool success) +{ + m_focusLockStatus = success ? QCamera::Locked : QCamera::Unlocked; + QCamera::LockChangeReason reason = success ? QCamera::LockAcquired : QCamera::LockFailed; + emit lockStatusChanged(QCamera::LockFocus, m_focusLockStatus, reason); +} + +void QAndroidCameraControl::onRecalculateTimeOut() +{ + if (m_exposureLockStatus == QCamera::Searching) { + m_cameraSession->camera()->setAutoExposureLock(true); + setExposureLockStatus(QCamera::Locked, QCamera::LockAcquired); + } + + if (m_whiteBalanceLockStatus == QCamera::Searching) { + m_cameraSession->camera()->setAutoWhiteBalanceLock(true); + setWhiteBalanceLockStatus(QCamera::Locked, QCamera::LockAcquired); + } +} + +void QAndroidCameraControl::onWhiteBalanceChanged() +{ + // changing the white balance mode releases the white balance lock + if (m_whiteBalanceLockStatus != QCamera::Unlocked) + setWhiteBalanceLockStatus(QCamera::Unlocked, QCamera::LockLost); +} + +void QAndroidCameraControl::setFocusLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + m_focusLockStatus = status; + emit lockStatusChanged(QCamera::LockFocus, m_focusLockStatus, reason); +} + +void QAndroidCameraControl::setWhiteBalanceLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + m_whiteBalanceLockStatus = status; + emit lockStatusChanged(QCamera::LockWhiteBalance, m_whiteBalanceLockStatus, reason); +} + +void QAndroidCameraControl::setExposureLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + m_exposureLockStatus = status; + emit lockStatusChanged(QCamera::LockExposure, m_exposureLockStatus, reason); +} + +QList<QCameraViewfinderSettings> QAndroidCameraControl::supportedViewfinderSettings() const +{ + QList<QCameraViewfinderSettings> viewfinderSettings; + + const QList<QSize> previewSizes = m_cameraSession->getSupportedPreviewSizes(); + const QList<QVideoFrame::PixelFormat> pixelFormats = m_cameraSession->getSupportedPixelFormats(); + const QList<AndroidCamera::FpsRange> fpsRanges = m_cameraSession->getSupportedPreviewFpsRange(); + + viewfinderSettings.reserve(previewSizes.size() * pixelFormats.size() * fpsRanges.size()); + + for (const QSize& size : previewSizes) { + for (QVideoFrame::PixelFormat pixelFormat : pixelFormats) { + for (const AndroidCamera::FpsRange& fpsRange : fpsRanges) { + QCameraViewfinderSettings s; + s.setResolution(size); + s.setPixelAspectRatio(QSize(1, 1)); + s.setPixelFormat(pixelFormat); + s.setMinimumFrameRate(fpsRange.getMinReal()); + s.setMaximumFrameRate(fpsRange.getMaxReal()); + viewfinderSettings << s; + } + } + } + return viewfinderSettings; +} + +QCameraViewfinderSettings QAndroidCameraControl::viewfinderSettings() const +{ + return m_cameraSession->viewfinderSettings(); +} + +void QAndroidCameraControl::setViewfinderSettings(const QCameraViewfinderSettings &settings) +{ + m_cameraSession->setViewfinderSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameracontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcameracontrol_p.h new file mode 100644 index 000000000..23fbc2dc6 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameracontrol_p.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERACONTROL_H +#define QANDROIDCAMERACONTROL_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 <qcameracontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCameraControl : public QCameraControl +{ + Q_OBJECT +public: + explicit QAndroidCameraControl(QAndroidCameraSession *session); + virtual ~QAndroidCameraControl(); + + QCamera::State state() const override; + void setState(QCamera::State state) override; + + QCamera::Status status() const override; + + QCamera::CaptureModes captureMode() const override; + void setCaptureMode(QCamera::CaptureModes mode) override; + bool isCaptureModeSupported(QCamera::CaptureModes mode) const override; + + bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const override; + + QCamera::LockTypes supportedLocks() const override; + QCamera::LockStatus lockStatus(QCamera::LockType lock) const override; + void searchAndLock(QCamera::LockTypes locks) override; + void unlock(QCamera::LockTypes locks) override; + + QList<QCameraViewfinderSettings> supportedViewfinderSettings() const override; + QCameraViewfinderSettings viewfinderSettings() const override; + void setViewfinderSettings(const QCameraViewfinderSettings &settings) override; + +private Q_SLOTS: + void onCameraOpened(); + void onCameraAutoFocusComplete(bool success); + void onRecalculateTimeOut(); + void onWhiteBalanceChanged(); + +private: + void setFocusLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + void setWhiteBalanceLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + void setExposureLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + + QAndroidCameraSession *m_cameraSession; + + QTimer *m_recalculateTimer; + + QCamera::LockTypes m_supportedLocks; + + QCamera::LockStatus m_focusLockStatus; + QCamera::LockStatus m_exposureLockStatus; + QCamera::LockStatus m_whiteBalanceLockStatus; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERACONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol.cpp new file mode 100644 index 000000000..e2c1d09c1 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol.cpp @@ -0,0 +1,355 @@ +/**************************************************************************** +** +** 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 "qandroidcameraexposurecontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidCameraExposureControl::QAndroidCameraExposureControl(QAndroidCameraSession *session) + : QCameraExposureControl() + , m_session(session) + , m_minExposureCompensationIndex(0) + , m_maxExposureCompensationIndex(0) + , m_exposureCompensationStep(0.0) + , m_requestedExposureCompensation(0.0) + , m_actualExposureCompensation(0.0) + , m_requestedExposureMode(QCameraExposure::ExposureAuto) + , m_actualExposureMode(QCameraExposure::ExposureAuto) +{ + connect(m_session, SIGNAL(opened()), + this, SLOT(onCameraOpened())); +} + +bool QAndroidCameraExposureControl::isParameterSupported(ExposureParameter parameter) const +{ + if (!m_session->camera()) + return false; + + switch (parameter) { + case QCameraExposureControl::ISO: + return false; + case QCameraExposureControl::Aperture: + return false; + case QCameraExposureControl::ShutterSpeed: + return false; + case QCameraExposureControl::ExposureCompensation: + return !m_supportedExposureCompensations.isEmpty(); + case QCameraExposureControl::TorchPower: + return false; + case QCameraExposureControl::ExposureMode: + return !m_supportedExposureModes.isEmpty(); + default: + return false; + } +} + +QVariantList QAndroidCameraExposureControl::supportedParameterRange(ExposureParameter parameter, bool *continuous) const +{ + if (!m_session->camera()) + return QVariantList(); + + if (continuous) + *continuous = false; + + if (parameter == QCameraExposureControl::ExposureCompensation) + return m_supportedExposureCompensations; + else if (parameter == QCameraExposureControl::ExposureMode) + return m_supportedExposureModes; + + return QVariantList(); +} + +QVariant QAndroidCameraExposureControl::requestedValue(ExposureParameter parameter) const +{ + if (parameter == QCameraExposureControl::ExposureCompensation) + return QVariant::fromValue(m_requestedExposureCompensation); + else if (parameter == QCameraExposureControl::ExposureMode) + return QVariant::fromValue(m_requestedExposureMode); + + return QVariant(); +} + +QVariant QAndroidCameraExposureControl::actualValue(ExposureParameter parameter) const +{ + if (parameter == QCameraExposureControl::ExposureCompensation) + return QVariant::fromValue(m_actualExposureCompensation); + else if (parameter == QCameraExposureControl::ExposureMode) + return QVariant::fromValue(m_actualExposureMode); + + return QVariant(); +} + +bool QAndroidCameraExposureControl::setValue(ExposureParameter parameter, const QVariant& value) +{ + if (!value.isValid()) + return false; + + if (parameter == QCameraExposureControl::ExposureCompensation) { + qreal expComp = value.toReal(); + if (!qFuzzyCompare(m_requestedExposureCompensation, expComp)) { + m_requestedExposureCompensation = expComp; + emit requestedValueChanged(QCameraExposureControl::ExposureCompensation); + } + + if (!m_session->camera()) + return true; + + int expCompIndex = qRound(m_requestedExposureCompensation / m_exposureCompensationStep); + if (expCompIndex >= m_minExposureCompensationIndex + && expCompIndex <= m_maxExposureCompensationIndex) { + qreal comp = expCompIndex * m_exposureCompensationStep; + m_session->camera()->setExposureCompensation(expCompIndex); + if (!qFuzzyCompare(m_actualExposureCompensation, comp)) { + m_actualExposureCompensation = expCompIndex * m_exposureCompensationStep; + emit actualValueChanged(QCameraExposureControl::ExposureCompensation); + } + + return true; + } + + } else if (parameter == QCameraExposureControl::ExposureMode) { + QCameraExposure::ExposureMode expMode = value.value<QCameraExposure::ExposureMode>(); + if (m_requestedExposureMode != expMode) { + m_requestedExposureMode = expMode; + emit requestedValueChanged(QCameraExposureControl::ExposureMode); + } + + if (!m_session->camera()) + return true; + + if (!m_supportedExposureModes.isEmpty()) { + m_actualExposureMode = m_requestedExposureMode; + + QString sceneMode; + switch (m_requestedExposureMode) { + case QCameraExposure::ExposureAuto: + sceneMode = QLatin1String("auto"); + break; + case QCameraExposure::ExposureSports: + sceneMode = QLatin1String("sports"); + break; + case QCameraExposure::ExposurePortrait: + sceneMode = QLatin1String("portrait"); + break; + case QCameraExposure::ExposureBeach: + sceneMode = QLatin1String("beach"); + break; + case QCameraExposure::ExposureSnow: + sceneMode = QLatin1String("snow"); + break; + case QCameraExposure::ExposureNight: + sceneMode = QLatin1String("night"); + break; + case QCameraExposure::ExposureAction: + sceneMode = QLatin1String("action"); + break; + case QCameraExposure::ExposureLandscape: + sceneMode = QLatin1String("landscape"); + break; + case QCameraExposure::ExposureNightPortrait: + sceneMode = QLatin1String("night-portrait"); + break; + case QCameraExposure::ExposureTheatre: + sceneMode = QLatin1String("theatre"); + break; + case QCameraExposure::ExposureSunset: + sceneMode = QLatin1String("sunset"); + break; + case QCameraExposure::ExposureSteadyPhoto: + sceneMode = QLatin1String("steadyphoto"); + break; + case QCameraExposure::ExposureFireworks: + sceneMode = QLatin1String("fireworks"); + break; + case QCameraExposure::ExposureParty: + sceneMode = QLatin1String("party"); + break; + case QCameraExposure::ExposureCandlelight: + sceneMode = QLatin1String("candlelight"); + break; + case QCameraExposure::ExposureBarcode: + sceneMode = QLatin1String("barcode"); + break; + default: + sceneMode = QLatin1String("auto"); + m_actualExposureMode = QCameraExposure::ExposureAuto; + break; + } + + m_session->camera()->setSceneMode(sceneMode); + emit actualValueChanged(QCameraExposureControl::ExposureMode); + + return true; + } + } + + return false; +} + +void QAndroidCameraExposureControl::onCameraOpened() +{ + m_supportedExposureCompensations.clear(); + m_minExposureCompensationIndex = m_session->camera()->getMinExposureCompensation(); + m_maxExposureCompensationIndex = m_session->camera()->getMaxExposureCompensation(); + m_exposureCompensationStep = m_session->camera()->getExposureCompensationStep(); + if (m_minExposureCompensationIndex != 0 || m_maxExposureCompensationIndex != 0) { + for (int i = m_minExposureCompensationIndex; i <= m_maxExposureCompensationIndex; ++i) + m_supportedExposureCompensations.append(i * m_exposureCompensationStep); + emit parameterRangeChanged(QCameraExposureControl::ExposureCompensation); + } + + m_supportedExposureModes.clear(); + QStringList sceneModes = m_session->camera()->getSupportedSceneModes(); + if (!sceneModes.isEmpty()) { + for (int i = 0; i < sceneModes.size(); ++i) { + const QString &sceneMode = sceneModes.at(i); + if (sceneMode == QLatin1String("auto")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureAuto); + else if (sceneMode == QLatin1String("beach")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureBeach); + else if (sceneMode == QLatin1String("night")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureNight); + else if (sceneMode == QLatin1String("portrait")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposurePortrait); + else if (sceneMode == QLatin1String("snow")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureSnow); + else if (sceneMode == QLatin1String("sports")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureSports); + else if (sceneMode == QLatin1String("action")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureAction); + else if (sceneMode == QLatin1String("landscape")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureLandscape); + else if (sceneMode == QLatin1String("night-portrait")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureNightPortrait); + else if (sceneMode == QLatin1String("theatre")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureTheatre); + else if (sceneMode == QLatin1String("sunset")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureSunset); + else if (sceneMode == QLatin1String("steadyphoto")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureSteadyPhoto); + else if (sceneMode == QLatin1String("fireworks")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureFireworks); + else if (sceneMode == QLatin1String("party")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureParty); + else if (sceneMode == QLatin1String("candlelight")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureCandlelight); + else if (sceneMode == QLatin1String("barcode")) + m_supportedExposureModes << QVariant::fromValue(QCameraExposure::ExposureBarcode); + } + emit parameterRangeChanged(QCameraExposureControl::ExposureMode); + } + + setValue(QCameraExposureControl::ExposureCompensation, QVariant::fromValue(m_requestedExposureCompensation)); + setValue(QCameraExposureControl::ExposureMode, QVariant::fromValue(m_requestedExposureMode)); + + m_supportedFlashModes.clear(); + + QStringList flashModes = m_session->camera()->getSupportedFlashModes(); + for (int i = 0; i < flashModes.size(); ++i) { + const QString &flashMode = flashModes.at(i); + if (flashMode == QLatin1String("off")) + m_supportedFlashModes << QCameraExposure::FlashOff; + else if (flashMode == QLatin1String("auto")) + m_supportedFlashModes << QCameraExposure::FlashAuto; + else if (flashMode == QLatin1String("on")) + m_supportedFlashModes << QCameraExposure::FlashOn; + else if (flashMode == QLatin1String("red-eye")) + m_supportedFlashModes << QCameraExposure::FlashRedEyeReduction; + else if (flashMode == QLatin1String("torch")) + m_supportedFlashModes << QCameraExposure::FlashVideoLight; + } + + if (!m_supportedFlashModes.contains(m_flashMode)) + m_flashMode = QCameraExposure::FlashOff; + + setFlashMode(m_flashMode); +} + + +QCameraExposure::FlashModes QAndroidCameraExposureControl::flashMode() const +{ + return m_flashMode; +} + +void QAndroidCameraExposureControl::setFlashMode(QCameraExposure::FlashModes mode) +{ + if (!m_session->camera()) { + m_flashMode = mode; + return; + } + + if (!isFlashModeSupported(mode)) + return; + + // if torch was enabled, it first needs to be turned off before setting another mode + if (m_flashMode == QCameraExposure::FlashVideoLight) + m_session->camera()->setFlashMode(QLatin1String("off")); + + m_flashMode = mode; + + QString flashMode; + if (mode.testFlag(QCameraExposure::FlashAuto)) + flashMode = QLatin1String("auto"); + else if (mode.testFlag(QCameraExposure::FlashOn)) + flashMode = QLatin1String("on"); + else if (mode.testFlag(QCameraExposure::FlashRedEyeReduction)) + flashMode = QLatin1String("red-eye"); + else if (mode.testFlag(QCameraExposure::FlashVideoLight)) + flashMode = QLatin1String("torch"); + else // FlashOff + flashMode = QLatin1String("off"); + + m_session->camera()->setFlashMode(flashMode); +} + +bool QAndroidCameraExposureControl::isFlashModeSupported(QCameraExposure::FlashModes mode) const +{ + return m_session->camera() ? m_supportedFlashModes.contains(mode) : false; +} + +bool QAndroidCameraExposureControl::isFlashReady() const +{ + // Android doesn't have an API for that + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol_p.h new file mode 100644 index 000000000..133221b13 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraexposurecontrol_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERAEXPOSURECONTROL_H +#define QANDROIDCAMERAEXPOSURECONTROL_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 <qcameraexposurecontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCameraExposureControl : public QCameraExposureControl +{ + Q_OBJECT +public: + explicit QAndroidCameraExposureControl(QAndroidCameraSession *session); + + bool isParameterSupported(ExposureParameter parameter) const override; + QVariantList supportedParameterRange(ExposureParameter parameter, bool *continuous) const override; + + QVariant requestedValue(ExposureParameter parameter) const override; + QVariant actualValue(ExposureParameter parameter) const override; + bool setValue(ExposureParameter parameter, const QVariant& value) override; + + QCameraExposure::FlashModes flashMode() const override; + void setFlashMode(QCameraExposure::FlashModes mode) override; + bool isFlashModeSupported(QCameraExposure::FlashModes mode) const override; + bool isFlashReady() const override; + +private Q_SLOTS: + void onCameraOpened(); + +private: + QAndroidCameraSession *m_session; + + QVariantList m_supportedExposureCompensations; + QVariantList m_supportedExposureModes; + + int m_minExposureCompensationIndex; + int m_maxExposureCompensationIndex; + qreal m_exposureCompensationStep; + + qreal m_requestedExposureCompensation; + qreal m_actualExposureCompensation; + QCameraExposure::ExposureMode m_requestedExposureMode; + QCameraExposure::ExposureMode m_actualExposureMode; + + QList<QCameraExposure::FlashModes> m_supportedFlashModes; + QCameraExposure::FlashModes m_flashMode; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERAEXPOSURECONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol.cpp new file mode 100644 index 000000000..0fa9b055a --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** 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 "qandroidcamerafocuscontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "androidcamera_p.h" + +#include "qandroidmultimediautils_p.h" +#include <qmath.h> + +QT_BEGIN_NAMESPACE + +static QRect adjustedArea(const QRectF &area) +{ + // Qt maps focus points in the range (0.0, 0.0) -> (1.0, 1.0) + // Android maps focus points in the range (-1000, -1000) -> (1000, 1000) + // Converts an area in Qt coordinates to Android coordinates + return QRect(-1000 + qRound(area.x() * 2000), + -1000 + qRound(area.y() * 2000), + qRound(area.width() * 2000), + qRound(area.height() * 2000)) + .intersected(QRect(-1000, -1000, 2000, 2000)); +} + +QAndroidCameraFocusControl::QAndroidCameraFocusControl(QAndroidCameraSession *session) + : QCameraFocusControl() + , m_session(session) + , m_focusMode(QCameraFocus::AutoFocus) + , m_focusPointMode(QCameraFocus::FocusPointAuto) + , m_actualFocusPoint(0.5, 0.5) + , m_continuousPictureFocusSupported(false) + , m_continuousVideoFocusSupported(false) +{ + connect(m_session, SIGNAL(opened()), + this, SLOT(onCameraOpened())); + connect(m_session, SIGNAL(captureModeChanged(QCamera::CaptureModes)), + this, SLOT(onCameraCaptureModeChanged())); +} + +QCameraFocus::FocusModes QAndroidCameraFocusControl::focusMode() const +{ + return m_focusMode; +} + +void QAndroidCameraFocusControl::setFocusMode(QCameraFocus::FocusModes mode) +{ + if (!m_session->camera()) { + setFocusModeHelper(mode); + return; + } + + if (isFocusModeSupported(mode)) { + QString focusMode = QLatin1String("fixed"); + + if (mode.testFlag(QCameraFocus::HyperfocalFocus)) { + focusMode = QLatin1String("edof"); + } else if (mode.testFlag(QCameraFocus::ManualFocus)) { + focusMode = QLatin1String("fixed"); + } else if (mode.testFlag(QCameraFocus::AutoFocus)) { + focusMode = QLatin1String("auto"); + } else if (mode.testFlag(QCameraFocus::MacroFocus)) { + focusMode = QLatin1String("macro"); + } else if (mode.testFlag(QCameraFocus::ContinuousFocus)) { + if ((m_session->captureMode().testFlag(QCamera::CaptureVideo) && m_continuousVideoFocusSupported) + || !m_continuousPictureFocusSupported) { + focusMode = QLatin1String("continuous-video"); + } else { + focusMode = QLatin1String("continuous-picture"); + } + } else if (mode.testFlag(QCameraFocus::InfinityFocus)) { + focusMode = QLatin1String("infinity"); + } + + m_session->camera()->setFocusMode(focusMode); + + // reset focus position + m_session->camera()->cancelAutoFocus(); + + setFocusModeHelper(mode); + } +} + +bool QAndroidCameraFocusControl::isFocusModeSupported(QCameraFocus::FocusModes mode) const +{ + return m_session->camera() ? m_supportedFocusModes.contains(mode) : false; +} + +QCameraFocus::FocusPointMode QAndroidCameraFocusControl::focusPointMode() const +{ + return m_focusPointMode; +} + +void QAndroidCameraFocusControl::setFocusPointMode(QCameraFocus::FocusPointMode mode) +{ + if (!m_session->camera()) { + setFocusPointModeHelper(mode); + return; + } + + if (isFocusPointModeSupported(mode)) { + if (mode == QCameraFocus::FocusPointCustom) { + m_actualFocusPoint = m_customFocusPoint; + } else { + // FocusPointAuto | FocusPointCenter + // note: there is no way to know the actual focus point in FocusPointAuto mode, + // so just report the focus point to be at the center of the frame + m_actualFocusPoint = QPointF(0.5, 0.5); + } + + setFocusPointModeHelper(mode); + + updateFocusZones(); + setCameraFocusArea(); + } +} + +bool QAndroidCameraFocusControl::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const +{ + return m_session->camera() ? m_supportedFocusPointModes.contains(mode) : false; +} + +QPointF QAndroidCameraFocusControl::customFocusPoint() const +{ + return m_customFocusPoint; +} + +void QAndroidCameraFocusControl::setCustomFocusPoint(const QPointF &point) +{ + if (m_customFocusPoint != point) { + m_customFocusPoint = point; + emit customFocusPointChanged(m_customFocusPoint); + } + + if (m_session->camera() && m_focusPointMode == QCameraFocus::FocusPointCustom) { + m_actualFocusPoint = m_customFocusPoint; + updateFocusZones(); + setCameraFocusArea(); + } +} + +QCameraFocusZoneList QAndroidCameraFocusControl::focusZones() const +{ + return m_focusZones; +} + +void QAndroidCameraFocusControl::onCameraOpened() +{ + connect(m_session->camera(), SIGNAL(previewSizeChanged()), + this, SLOT(onViewportSizeChanged())); + connect(m_session->camera(), SIGNAL(autoFocusStarted()), + this, SLOT(onAutoFocusStarted())); + connect(m_session->camera(), SIGNAL(autoFocusComplete(bool)), + this, SLOT(onAutoFocusComplete(bool))); + + m_supportedFocusModes.clear(); + m_continuousPictureFocusSupported = false; + m_continuousVideoFocusSupported = false; + m_supportedFocusPointModes.clear(); + + QStringList focusModes = m_session->camera()->getSupportedFocusModes(); + for (int i = 0; i < focusModes.size(); ++i) { + const QString &focusMode = focusModes.at(i); + if (focusMode == QLatin1String("auto")) { + m_supportedFocusModes << QCameraFocus::AutoFocus; + } else if (focusMode == QLatin1String("continuous-picture")) { + m_supportedFocusModes << QCameraFocus::ContinuousFocus; + m_continuousPictureFocusSupported = true; + } else if (focusMode == QLatin1String("continuous-video")) { + m_supportedFocusModes << QCameraFocus::ContinuousFocus; + m_continuousVideoFocusSupported = true; + } else if (focusMode == QLatin1String("edof")) { + m_supportedFocusModes << QCameraFocus::HyperfocalFocus; + } else if (focusMode == QLatin1String("fixed")) { + m_supportedFocusModes << QCameraFocus::ManualFocus; + } else if (focusMode == QLatin1String("infinity")) { + m_supportedFocusModes << QCameraFocus::InfinityFocus; + } else if (focusMode == QLatin1String("macro")) { + m_supportedFocusModes << QCameraFocus::MacroFocus; + } + } + + m_supportedFocusPointModes << QCameraFocus::FocusPointAuto; + if (m_session->camera()->getMaxNumFocusAreas() > 0) + m_supportedFocusPointModes << QCameraFocus::FocusPointCenter << QCameraFocus::FocusPointCustom; + + if (!m_supportedFocusModes.contains(m_focusMode)) + setFocusModeHelper(QCameraFocus::AutoFocus); + if (!m_supportedFocusPointModes.contains(m_focusPointMode)) + setFocusPointModeHelper(QCameraFocus::FocusPointAuto); + + setFocusMode(m_focusMode); + setCustomFocusPoint(m_customFocusPoint); + setFocusPointMode(m_focusPointMode); + + if (m_session->camera()->isZoomSupported()) { + m_zoomRatios = m_session->camera()->getZoomRatios(); + qreal maxZoom = m_zoomRatios.last() / qreal(100); + if (m_maximumZoom != maxZoom) { + m_maximumZoom = maxZoom; + emit maximumDigitalZoomChanged(m_maximumZoom); + } + zoomTo(1, m_requestedZoom); + } else { + m_zoomRatios.clear(); + if (!qFuzzyCompare(m_maximumZoom, qreal(1))) { + m_maximumZoom = 1.0; + emit maximumDigitalZoomChanged(m_maximumZoom); + } + } +} + +void QAndroidCameraFocusControl::updateFocusZones(QCameraFocusZone::FocusZoneStatus status) +{ + if (!m_session->camera()) + return; + + // create a focus zone (50x50 pixel) around the focus point + m_focusZones.clear(); + + if (!m_actualFocusPoint.isNull()) { + QSize viewportSize = m_session->camera()->previewSize(); + + if (!viewportSize.isValid()) + return; + + QSizeF focusSize(50.f / viewportSize.width(), 50.f / viewportSize.height()); + float x = qBound(qreal(0), + m_actualFocusPoint.x() - (focusSize.width() / 2), + 1.f - focusSize.width()); + float y = qBound(qreal(0), + m_actualFocusPoint.y() - (focusSize.height() / 2), + 1.f - focusSize.height()); + + QRectF area(QPointF(x, y), focusSize); + + m_focusZones.append(QCameraFocusZone(area, status)); + } + + emit focusZonesChanged(); +} + +void QAndroidCameraFocusControl::setCameraFocusArea() +{ + QList<QRect> areas; + if (m_focusPointMode != QCameraFocus::FocusPointAuto) { + // in FocusPointAuto mode, leave the area list empty + // to let the driver choose the focus point. + + for (int i = 0; i < m_focusZones.size(); ++i) + areas.append(adjustedArea(m_focusZones.at(i).area())); + + } + m_session->camera()->setFocusAreas(areas); +} + +void QAndroidCameraFocusControl::onViewportSizeChanged() +{ + QCameraFocusZone::FocusZoneStatus status = QCameraFocusZone::Selected; + if (!m_focusZones.isEmpty()) + status = m_focusZones.at(0).status(); + updateFocusZones(status); + setCameraFocusArea(); +} + +void QAndroidCameraFocusControl::onCameraCaptureModeChanged() +{ + if (m_session->camera() && m_focusMode == QCameraFocus::ContinuousFocus) { + QString focusMode; + if ((m_session->captureMode().testFlag(QCamera::CaptureVideo) && m_continuousVideoFocusSupported) + || !m_continuousPictureFocusSupported) { + focusMode = QLatin1String("continuous-video"); + } else { + focusMode = QLatin1String("continuous-picture"); + } + m_session->camera()->setFocusMode(focusMode); + m_session->camera()->cancelAutoFocus(); + } +} + +void QAndroidCameraFocusControl::onAutoFocusStarted() +{ + updateFocusZones(QCameraFocusZone::Selected); +} + +void QAndroidCameraFocusControl::onAutoFocusComplete(bool success) +{ + if (success) + updateFocusZones(QCameraFocusZone::Focused); +} + + +qreal QAndroidCameraFocusControl::maximumOpticalZoom() const +{ + // Optical zoom not supported + return 1.0; +} + +qreal QAndroidCameraFocusControl::maximumDigitalZoom() const +{ + return m_maximumZoom; +} + +qreal QAndroidCameraFocusControl::requestedOpticalZoom() const +{ + // Optical zoom not supported + return 1.0; +} + +qreal QAndroidCameraFocusControl::requestedDigitalZoom() const +{ + return m_requestedZoom; +} + +qreal QAndroidCameraFocusControl::currentOpticalZoom() const +{ + // Optical zoom not supported + return 1.0; +} + +qreal QAndroidCameraFocusControl::currentDigitalZoom() const +{ + return m_currentZoom; +} + +void QAndroidCameraFocusControl::zoomTo(qreal optical, qreal digital) +{ + Q_UNUSED(optical); + + if (!qFuzzyCompare(m_requestedZoom, digital)) { + m_requestedZoom = digital; + emit requestedDigitalZoomChanged(m_requestedZoom); + } + + if (m_session->camera()) { + digital = qBound(qreal(1), digital, m_maximumZoom); + int validZoomIndex = qt_findClosestValue(m_zoomRatios, qRound(digital * 100)); + qreal newZoom = m_zoomRatios.at(validZoomIndex) / qreal(100); + if (!qFuzzyCompare(m_currentZoom, newZoom)) { + m_session->camera()->setZoom(validZoomIndex); + m_currentZoom = newZoom; + emit currentDigitalZoomChanged(m_currentZoom); + } + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol_p.h new file mode 100644 index 000000000..4cc931993 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcamerafocuscontrol_p.h @@ -0,0 +1,133 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERAFOCUSCONTROL_H +#define QANDROIDCAMERAFOCUSCONTROL_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 <qcamerafocuscontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCameraFocusControl : public QCameraFocusControl +{ + Q_OBJECT +public: + explicit QAndroidCameraFocusControl(QAndroidCameraSession *session); + + QCameraFocus::FocusModes focusMode() const override; + void setFocusMode(QCameraFocus::FocusModes mode) override; + bool isFocusModeSupported(QCameraFocus::FocusModes mode) const override; + QCameraFocus::FocusPointMode focusPointMode() const override; + void setFocusPointMode(QCameraFocus::FocusPointMode mode) override; + bool isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const override; + QPointF customFocusPoint() const override; + void setCustomFocusPoint(const QPointF &point) override; + QCameraFocusZoneList focusZones() const override; + + qreal maximumOpticalZoom() const override; + qreal maximumDigitalZoom() const override; + qreal requestedOpticalZoom() const override; + qreal requestedDigitalZoom() const override; + qreal currentOpticalZoom() const override; + qreal currentDigitalZoom() const override; + void zoomTo(qreal optical, qreal digital) override; + +private Q_SLOTS: + void onCameraOpened(); + void onViewportSizeChanged(); + void onCameraCaptureModeChanged(); + void onAutoFocusStarted(); + void onAutoFocusComplete(bool success); + +private: + inline void setFocusModeHelper(QCameraFocus::FocusModes mode) + { + if (m_focusMode != mode) { + m_focusMode = mode; + emit focusModeChanged(mode); + } + } + + inline void setFocusPointModeHelper(QCameraFocus::FocusPointMode mode) + { + if (m_focusPointMode != mode) { + m_focusPointMode = mode; + emit focusPointModeChanged(mode); + } + } + + void updateFocusZones(QCameraFocusZone::FocusZoneStatus status = QCameraFocusZone::Selected); + void setCameraFocusArea(); + + QAndroidCameraSession *m_session; + + QCameraFocus::FocusModes m_focusMode; + QCameraFocus::FocusPointMode m_focusPointMode; + QPointF m_actualFocusPoint; + QPointF m_customFocusPoint; + QCameraFocusZoneList m_focusZones; + + QList<QCameraFocus::FocusModes> m_supportedFocusModes; + bool m_continuousPictureFocusSupported; + bool m_continuousVideoFocusSupported; + + QList<QCameraFocus::FocusPointMode> m_supportedFocusPointModes; + + qreal m_maximumZoom; + QList<int> m_zoomRatios; + qreal m_requestedZoom; + qreal m_currentZoom; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERAFOCUSCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol.cpp new file mode 100644 index 000000000..d230daa5c --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** 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 "qandroidcameraimagecapturecontrol_p.h" + +#include "qandroidcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidCameraImageCaptureControl::QAndroidCameraImageCaptureControl(QAndroidCameraSession *session) + : QCameraImageCaptureControl() + , m_session(session) +{ + connect(m_session, SIGNAL(readyForCaptureChanged(bool)), this, SIGNAL(readyForCaptureChanged(bool))); + connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int))); + connect(m_session, SIGNAL(imageCaptured(int,QImage)), this, SIGNAL(imageCaptured(int,QImage))); + connect(m_session, SIGNAL(imageMetadataAvailable(int,QString,QVariant)), this, SIGNAL(imageMetadataAvailable(int,QString,QVariant))); + connect(m_session, SIGNAL(imageAvailable(int,QVideoFrame)), this, SIGNAL(imageAvailable(int,QVideoFrame))); + connect(m_session, SIGNAL(imageSaved(int,QString)), this, SIGNAL(imageSaved(int,QString))); + connect(m_session, SIGNAL(imageCaptureError(int,int,QString)), this, SIGNAL(error(int,int,QString))); +} + +bool QAndroidCameraImageCaptureControl::isReadyForCapture() const +{ + return m_session->isReadyForCapture(); +} + +QCameraImageCapture::DriveMode QAndroidCameraImageCaptureControl::driveMode() const +{ + return m_session->driveMode(); +} + +void QAndroidCameraImageCaptureControl::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_session->setDriveMode(mode); +} + +int QAndroidCameraImageCaptureControl::capture(const QString &fileName) +{ + return m_session->capture(fileName); +} + +void QAndroidCameraImageCaptureControl::cancelCapture() +{ + m_session->cancelCapture(); +} + +QCameraImageCapture::CaptureDestinations QAndroidCameraImageCaptureControl::captureDestination() const +{ + return m_session->captureDestination();; +} + +void QAndroidCameraImageCaptureControl::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + m_session->setCaptureDestination(destination); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol_p.h new file mode 100644 index 000000000..071e8a314 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraimagecapturecontrol_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERAIMAGECAPTURECONTROL_H +#define QANDROIDCAMERAIMAGECAPTURECONTROL_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 <qcameraimagecapturecontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCameraImageCaptureControl : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + explicit QAndroidCameraImageCaptureControl(QAndroidCameraSession *session); + + bool isReadyForCapture() const override; + + QCameraImageCapture::DriveMode driveMode() const override; + void setDriveMode(QCameraImageCapture::DriveMode mode) override; + + int capture(const QString &fileName) override; + void cancelCapture() override; + + QCameraImageCapture::CaptureDestinations captureDestination() const override; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) override; + +private: + QAndroidCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERAIMAGECAPTURECONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol.cpp new file mode 100644 index 000000000..f35e6cf6e --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol.cpp @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** 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 "qandroidcameraimageprocessingcontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidCameraImageProcessingControl::QAndroidCameraImageProcessingControl(QAndroidCameraSession *session) + : QCameraImageProcessingControl() + , m_session(session) + , m_whiteBalanceMode(QCameraImageProcessing::WhiteBalanceAuto) +{ + connect(m_session, SIGNAL(opened()), + this, SLOT(onCameraOpened())); +} + +bool QAndroidCameraImageProcessingControl::isParameterSupported(ProcessingParameter parameter) const +{ + return parameter == QCameraImageProcessingControl::WhiteBalancePreset + && m_session->camera() + && !m_supportedWhiteBalanceModes.isEmpty(); +} + +bool QAndroidCameraImageProcessingControl::isParameterValueSupported(ProcessingParameter parameter, + const QVariant &value) const +{ + return parameter == QCameraImageProcessingControl::WhiteBalancePreset + && m_session->camera() + && m_supportedWhiteBalanceModes.contains(value.value<QCameraImageProcessing::WhiteBalanceMode>()); +} + +QVariant QAndroidCameraImageProcessingControl::parameter(ProcessingParameter parameter) const +{ + if (parameter != QCameraImageProcessingControl::WhiteBalancePreset) + return QVariant(); + + return QVariant::fromValue(m_whiteBalanceMode); +} + +void QAndroidCameraImageProcessingControl::setParameter(ProcessingParameter parameter, const QVariant &value) +{ + if (parameter != QCameraImageProcessingControl::WhiteBalancePreset) + return; + + QCameraImageProcessing::WhiteBalanceMode mode = value.value<QCameraImageProcessing::WhiteBalanceMode>(); + + if (m_session->camera()) + setWhiteBalanceModeHelper(mode); + else + m_whiteBalanceMode = mode; +} + +void QAndroidCameraImageProcessingControl::setWhiteBalanceModeHelper(QCameraImageProcessing::WhiteBalanceMode mode) +{ + QString wb = m_supportedWhiteBalanceModes.value(mode, QString()); + if (!wb.isEmpty()) { + m_session->camera()->setWhiteBalance(wb); + m_whiteBalanceMode = mode; + } +} + +void QAndroidCameraImageProcessingControl::onCameraOpened() +{ + m_supportedWhiteBalanceModes.clear(); + QStringList whiteBalanceModes = m_session->camera()->getSupportedWhiteBalance(); + for (int i = 0; i < whiteBalanceModes.size(); ++i) { + const QString &wb = whiteBalanceModes.at(i); + if (wb == QLatin1String("auto")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceAuto, + QStringLiteral("auto")); + } else if (wb == QLatin1String("cloudy-daylight")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceCloudy, + QStringLiteral("cloudy-daylight")); + } else if (wb == QLatin1String("daylight")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceSunlight, + QStringLiteral("daylight")); + } else if (wb == QLatin1String("fluorescent")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceFluorescent, + QStringLiteral("fluorescent")); + } else if (wb == QLatin1String("incandescent")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceTungsten, + QStringLiteral("incandescent")); + } else if (wb == QLatin1String("shade")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceShade, + QStringLiteral("shade")); + } else if (wb == QLatin1String("twilight")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceSunset, + QStringLiteral("twilight")); + } else if (wb == QLatin1String("warm-fluorescent")) { + m_supportedWhiteBalanceModes.insert(QCameraImageProcessing::WhiteBalanceFlash, + QStringLiteral("warm-fluorescent")); + } + } + + if (!m_supportedWhiteBalanceModes.contains(m_whiteBalanceMode)) + m_whiteBalanceMode = QCameraImageProcessing::WhiteBalanceAuto; + + setWhiteBalanceModeHelper(m_whiteBalanceMode); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol_p.h new file mode 100644 index 000000000..370c8dfb6 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameraimageprocessingcontrol_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERAIMAGEPROCESSINGCONTROL_H +#define QANDROIDCAMERAIMAGEPROCESSINGCONTROL_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 <qcameraimageprocessingcontrol_p.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCameraImageProcessingControl : public QCameraImageProcessingControl +{ + Q_OBJECT +public: + explicit QAndroidCameraImageProcessingControl(QAndroidCameraSession *session); + + bool isParameterSupported(ProcessingParameter) const override; + bool isParameterValueSupported(ProcessingParameter parameter, const QVariant &value) const override; + QVariant parameter(ProcessingParameter parameter) const override; + void setParameter(ProcessingParameter parameter, const QVariant &value) override; + +private Q_SLOTS: + void onCameraOpened(); + +private: + void setWhiteBalanceModeHelper(QCameraImageProcessing::WhiteBalanceMode mode); + + QAndroidCameraSession *m_session; + + QCameraImageProcessing::WhiteBalanceMode m_whiteBalanceMode; + + QMap<QCameraImageProcessing::WhiteBalanceMode, QString> m_supportedWhiteBalanceModes; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERAIMAGEPROCESSINGCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp b/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp new file mode 100644 index 000000000..f261d86e2 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcamerasession.cpp @@ -0,0 +1,939 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 "qandroidcamerasession_p.h" + +#include "androidcamera_p.h" +#include "androidmultimediautils_p.h" +#include "qandroidvideooutput_p.h" +#include "qandroidmediavideoprobecontrol_p.h" +#include "qandroidmultimediautils_p.h" +#include "qandroidcameravideorenderercontrol_p.h" +#include <qabstractvideosurface.h> +#include <QtConcurrent/qtconcurrentrun.h> +#include <qfile.h> +#include <qguiapplication.h> +#include <qdebug.h> +#include <qvideoframe.h> +#include <private/qmemoryvideobuffer_p.h> +#include <QtCore/private/qjnihelpers_p.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC(QList<AndroidCameraInfo>, g_availableCameras) + +QAndroidCameraSession::QAndroidCameraSession(QObject *parent) + : QObject(parent) + , m_selectedCamera(0) + , m_camera(0) + , m_nativeOrientation(0) + , m_videoOutput(0) + , m_captureMode(QCamera::CaptureStillImage) + , m_state(QCamera::UnloadedState) + , m_savedState(-1) + , m_status(QCamera::UnloadedStatus) + , m_previewStarted(false) + , m_captureDestination(QCameraImageCapture::CaptureToFile) + , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture) + , m_lastImageCaptureId(0) + , m_readyForCapture(false) + , m_captureCanceled(false) + , m_currentImageCaptureId(-1) + , m_previewCallback(0) + , m_keepActive(false) +{ + m_mediaStorageLocation.addStorageLocation( + QMediaStorageLocation::Pictures, + AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::DCIM)); + + if (qApp) { + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), + this, SLOT(onApplicationStateChanged(Qt::ApplicationState))); + } +} + +QAndroidCameraSession::~QAndroidCameraSession() +{ + close(); +} + +void QAndroidCameraSession::setCaptureMode(QCamera::CaptureModes mode) +{ + if (m_captureMode == mode || !isCaptureModeSupported(mode)) + return; + + m_captureMode = mode; + emit captureModeChanged(m_captureMode); + + if (m_previewStarted && m_captureMode.testFlag(QCamera::CaptureStillImage)) + applyViewfinderSettings(m_actualImageSettings.resolution()); +} + +bool QAndroidCameraSession::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + if (mode & (QCamera::CaptureStillImage & QCamera::CaptureVideo)) + return false; + + return true; +} + +void QAndroidCameraSession::setState(QCamera::State state) +{ + if (m_state == state) + return; + + m_state = state; + emit stateChanged(m_state); + + // If the application is inactive, the camera shouldn't be started. Save the desired state + // instead and it will be set when the application becomes active. + if (qApp->applicationState() == Qt::ApplicationActive) + setStateHelper(state); + else + m_savedState = state; +} + +void QAndroidCameraSession::setStateHelper(QCamera::State state) +{ + switch (state) { + case QCamera::UnloadedState: + close(); + break; + case QCamera::LoadedState: + case QCamera::ActiveState: + if (!m_camera && !open()) { + m_state = QCamera::UnloadedState; + emit stateChanged(m_state); + emit error(QCamera::CameraError, QStringLiteral("Failed to open camera")); + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); + return; + } + if (state == QCamera::ActiveState) + startPreview(); + else if (state == QCamera::LoadedState) + stopPreview(); + break; + } +} + +void QAndroidCameraSession::updateAvailableCameras() +{ + g_availableCameras->clear(); + + const int numCameras = AndroidCamera::getNumberOfCameras(); + for (int i = 0; i < numCameras; ++i) { + AndroidCameraInfo info; + AndroidCamera::getCameraInfo(i, &info); + + if (!info.name.isNull()) + g_availableCameras->append(info); + } +} + +const QList<AndroidCameraInfo> &QAndroidCameraSession::availableCameras() +{ + if (g_availableCameras->isEmpty()) + updateAvailableCameras(); + + return *g_availableCameras; +} + +bool QAndroidCameraSession::open() +{ + close(); + + m_status = QCamera::LoadingStatus; + emit statusChanged(m_status); + + m_camera = AndroidCamera::open(m_selectedCamera); + + if (m_camera) { + connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed())); + connect(m_camera, SIGNAL(lastPreviewFrameFetched(QVideoFrame)), + this, SLOT(onLastPreviewFrameFetched(QVideoFrame)), + Qt::DirectConnection); + connect(m_camera, SIGNAL(newPreviewFrame(QVideoFrame)), + this, SLOT(onNewPreviewFrame(QVideoFrame)), + Qt::DirectConnection); + connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray))); + connect(m_camera, SIGNAL(previewStarted()), this, SLOT(onCameraPreviewStarted())); + connect(m_camera, SIGNAL(previewStopped()), this, SLOT(onCameraPreviewStopped())); + connect(m_camera, &AndroidCamera::previewFailedToStart, this, &QAndroidCameraSession::onCameraPreviewFailedToStart); + connect(m_camera, &AndroidCamera::takePictureFailed, this, &QAndroidCameraSession::onCameraTakePictureFailed); + + m_nativeOrientation = m_camera->getNativeOrientation(); + + m_status = QCamera::LoadedStatus; + + if (m_camera->getPreviewFormat() != AndroidCamera::NV21) + m_camera->setPreviewFormat(AndroidCamera::NV21); + + m_camera->notifyNewFrames(m_videoProbes.count() || m_previewCallback); + + emit opened(); + emit statusChanged(m_status); + } + + return m_camera != 0; +} + +void QAndroidCameraSession::close() +{ + if (!m_camera) + return; + + stopPreview(); + + m_status = QCamera::UnloadingStatus; + emit statusChanged(m_status); + + m_readyForCapture = false; + m_currentImageCaptureId = -1; + m_currentImageCaptureFileName.clear(); + m_actualImageSettings = m_requestedImageSettings; + m_actualViewfinderSettings = m_requestedViewfinderSettings; + + m_camera->release(); + delete m_camera; + m_camera = 0; + + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); +} + +void QAndroidCameraSession::setVideoOutput(QAndroidVideoOutput *output) +{ + if (m_videoOutput) { + m_videoOutput->stop(); + m_videoOutput->reset(); + } + + if (output) { + m_videoOutput = output; + if (m_videoOutput->isReady()) + onVideoOutputReady(true); + else + connect(m_videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool))); + } else { + m_videoOutput = 0; + } +} + +void QAndroidCameraSession::setViewfinderSettings(const QCameraViewfinderSettings &settings) +{ + if (m_requestedViewfinderSettings == settings) + return; + + m_requestedViewfinderSettings = m_actualViewfinderSettings = settings; + + if (m_readyForCapture) + applyViewfinderSettings(); +} + +void QAndroidCameraSession::applyViewfinderSettings(const QSize &captureSize, bool restartPreview) +{ + if (!m_camera) + return; + + const QSize currentViewfinderResolution = m_camera->previewSize(); + const AndroidCamera::ImageFormat currentPreviewFormat = m_camera->getPreviewFormat(); + const AndroidCamera::FpsRange currentFpsRange = m_camera->getPreviewFpsRange(); + + // -- adjust resolution + QSize adjustedViewfinderResolution; + const bool validCaptureSize = captureSize.width() > 0 && captureSize.height() > 0; + if (m_captureMode.testFlag(QCamera::CaptureVideo) + && validCaptureSize + && m_camera->getPreferredPreviewSizeForVideo().isEmpty()) { + // According to the Android doc, if getPreferredPreviewSizeForVideo() returns null, it means + // the preview size cannot be different from the capture size + adjustedViewfinderResolution = captureSize; + } else { + qreal captureAspectRatio = 0; + if (validCaptureSize) + captureAspectRatio = qreal(captureSize.width()) / qreal(captureSize.height()); + + const QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes(); + + const QSize vfRes = m_requestedViewfinderSettings.resolution(); + if (vfRes.width() > 0 && vfRes.height() > 0 + && (!validCaptureSize || qAbs(captureAspectRatio - (qreal(vfRes.width()) / vfRes.height())) < 0.01) + && previewSizes.contains(vfRes)) { + adjustedViewfinderResolution = vfRes; + } else if (validCaptureSize) { + // search for viewfinder resolution with the same aspect ratio + qreal minAspectDiff = 1; + QSize closestResolution; + for (int i = previewSizes.count() - 1; i >= 0; --i) { + const QSize &size = previewSizes.at(i); + const qreal sizeAspect = qreal(size.width()) / size.height(); + if (qFuzzyCompare(captureAspectRatio, sizeAspect)) { + adjustedViewfinderResolution = size; + break; + } else if (minAspectDiff > qAbs(sizeAspect - captureAspectRatio)) { + closestResolution = size; + minAspectDiff = qAbs(sizeAspect - captureAspectRatio); + } + } + if (!adjustedViewfinderResolution.isValid()) { + qWarning("Cannot find a viewfinder resolution matching the capture aspect ratio."); + if (closestResolution.isValid()) { + adjustedViewfinderResolution = closestResolution; + qWarning("Using closest viewfinder resolution."); + } else { + return; + } + } + } else { + adjustedViewfinderResolution = previewSizes.last(); + } + } + m_actualViewfinderSettings.setResolution(adjustedViewfinderResolution); + + // -- adjust pixel format + + AndroidCamera::ImageFormat adjustedPreviewFormat = AndroidCamera::NV21; + if (m_requestedViewfinderSettings.pixelFormat() != QVideoFrame::Format_Invalid) { + const AndroidCamera::ImageFormat f = AndroidImageFormatFromQtPixelFormat(m_requestedViewfinderSettings.pixelFormat()); + if (f == AndroidCamera::UnknownImageFormat || !m_camera->getSupportedPreviewFormats().contains(f)) + qWarning("Unsupported viewfinder pixel format"); + else + adjustedPreviewFormat = f; + } + m_actualViewfinderSettings.setPixelFormat(QtPixelFormatFromAndroidImageFormat(adjustedPreviewFormat)); + + // -- adjust FPS + + AndroidCamera::FpsRange adjustedFps = currentFpsRange; + const AndroidCamera::FpsRange requestedFpsRange = AndroidCamera::FpsRange::makeFromQReal(m_requestedViewfinderSettings.minimumFrameRate(), + m_requestedViewfinderSettings.maximumFrameRate()); + if (requestedFpsRange.min > 0 || requestedFpsRange.max > 0) { + int minDist = INT_MAX; + const QList<AndroidCamera::FpsRange> supportedFpsRanges = m_camera->getSupportedPreviewFpsRange(); + auto it = supportedFpsRanges.rbegin(), end = supportedFpsRanges.rend(); + for (; it != end; ++it) { + int dist = (requestedFpsRange.min > 0 ? qAbs(requestedFpsRange.min - it->min) : 0) + + (requestedFpsRange.max > 0 ? qAbs(requestedFpsRange.max - it->max) : 0); + if (dist < minDist) { + minDist = dist; + adjustedFps = *it; + if (minDist == 0) + break; // exact match + } + } + } + m_actualViewfinderSettings.setMinimumFrameRate(adjustedFps.getMinReal()); + m_actualViewfinderSettings.setMaximumFrameRate(adjustedFps.getMaxReal()); + + // -- Set values on camera + + if (currentViewfinderResolution != adjustedViewfinderResolution + || currentPreviewFormat != adjustedPreviewFormat + || currentFpsRange.min != adjustedFps.min + || currentFpsRange.max != adjustedFps.max) { + + if (m_videoOutput) + m_videoOutput->setVideoSize(adjustedViewfinderResolution); + + // if preview is started, we have to stop it first before changing its size + if (m_previewStarted && restartPreview) + m_camera->stopPreview(); + + m_camera->setPreviewSize(adjustedViewfinderResolution); + m_camera->setPreviewFormat(adjustedPreviewFormat); + m_camera->setPreviewFpsRange(adjustedFps); + + // restart preview + if (m_previewStarted && restartPreview) + m_camera->startPreview(); + } +} + +QList<QSize> QAndroidCameraSession::getSupportedPreviewSizes() const +{ + return m_camera ? m_camera->getSupportedPreviewSizes() : QList<QSize>(); +} + +QList<QVideoFrame::PixelFormat> QAndroidCameraSession::getSupportedPixelFormats() const +{ + QList<QVideoFrame::PixelFormat> formats; + + if (!m_camera) + return formats; + + const QList<AndroidCamera::ImageFormat> nativeFormats = m_camera->getSupportedPreviewFormats(); + + formats.reserve(nativeFormats.size()); + + for (AndroidCamera::ImageFormat nativeFormat : nativeFormats) { + QVideoFrame::PixelFormat format = QtPixelFormatFromAndroidImageFormat(nativeFormat); + if (format != QVideoFrame::Format_Invalid) + formats.append(format); + } + + return formats; +} + +QList<AndroidCamera::FpsRange> QAndroidCameraSession::getSupportedPreviewFpsRange() const +{ + return m_camera ? m_camera->getSupportedPreviewFpsRange() : QList<AndroidCamera::FpsRange>(); +} + +struct NullSurface : QAbstractVideoSurface +{ + NullSurface(QObject *parent = nullptr) : QAbstractVideoSurface(parent) { } + QList<QVideoFrame::PixelFormat> supportedPixelFormats( + QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const override + { + QList<QVideoFrame::PixelFormat> result; + if (type == QAbstractVideoBuffer::NoHandle) + result << QVideoFrame::Format_NV21; + + return result; + } + + bool present(const QVideoFrame &) { return false; } +}; + +bool QAndroidCameraSession::startPreview() +{ + if (!m_camera) + return false; + + if (m_previewStarted) + return true; + + if (m_videoOutput) { + if (!m_videoOutput->isReady()) + return true; // delay starting until the video output is ready + + Q_ASSERT(m_videoOutput->surfaceTexture() || m_videoOutput->surfaceHolder()); + + if ((m_videoOutput->surfaceTexture() && !m_camera->setPreviewTexture(m_videoOutput->surfaceTexture())) + || (m_videoOutput->surfaceHolder() && !m_camera->setPreviewDisplay(m_videoOutput->surfaceHolder()))) + return false; + } else { + auto control = new QAndroidCameraVideoRendererControl(this, this); + control->setSurface(new NullSurface(this)); + qWarning() << "Starting camera without viewfinder available"; + + return true; + } + + m_status = QCamera::StartingStatus; + emit statusChanged(m_status); + + applyImageSettings(); + applyViewfinderSettings(m_captureMode.testFlag(QCamera::CaptureStillImage) ? m_actualImageSettings.resolution() + : QSize()); + + AndroidMultimediaUtils::enableOrientationListener(true); + + // Before API level 24 the orientation was always 0, which is what we're expecting, so + // we'll enforce that here. + if (QtAndroidPrivate::androidSdkVersion() > 23) + m_camera->setDisplayOrientation(0); + + m_camera->startPreview(); + m_previewStarted = true; + + return true; +} + +void QAndroidCameraSession::stopPreview() +{ + if (!m_camera || !m_previewStarted) + return; + + m_status = QCamera::StoppingStatus; + emit statusChanged(m_status); + + AndroidMultimediaUtils::enableOrientationListener(false); + + m_camera->stopPreview(); + m_camera->setPreviewSize(QSize()); + m_camera->setPreviewTexture(0); + m_camera->setPreviewDisplay(0); + + if (m_videoOutput) { + m_videoOutput->stop(); + m_videoOutput->reset(); + } + m_previewStarted = false; +} + +void QAndroidCameraSession::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_requestedImageSettings == settings) + return; + + m_requestedImageSettings = m_actualImageSettings = settings; + + applyImageSettings(); + + if (m_readyForCapture && m_captureMode.testFlag(QCamera::CaptureStillImage)) + applyViewfinderSettings(m_actualImageSettings.resolution()); +} + +int QAndroidCameraSession::currentCameraRotation() const +{ + if (!m_camera) + return 0; + + // subtract natural camera orientation and physical device orientation + int rotation = 0; + int deviceOrientation = (AndroidMultimediaUtils::getDeviceOrientation() + 45) / 90 * 90; + if (m_camera->getFacing() == AndroidCamera::CameraFacingFront) + rotation = (m_nativeOrientation - deviceOrientation + 360) % 360; + else // back-facing camera + rotation = (m_nativeOrientation + deviceOrientation) % 360; + + return rotation; +} + +void QAndroidCameraSession::addProbe(QAndroidMediaVideoProbeControl *probe) +{ + m_videoProbesMutex.lock(); + if (probe) + m_videoProbes << probe; + if (m_camera) + m_camera->notifyNewFrames(m_videoProbes.count() || m_previewCallback); + m_videoProbesMutex.unlock(); +} + +void QAndroidCameraSession::removeProbe(QAndroidMediaVideoProbeControl *probe) +{ + m_videoProbesMutex.lock(); + m_videoProbes.remove(probe); + if (m_camera) + m_camera->notifyNewFrames(m_videoProbes.count() || m_previewCallback); + m_videoProbesMutex.unlock(); +} + +void QAndroidCameraSession::setPreviewFormat(AndroidCamera::ImageFormat format) +{ + if (format == AndroidCamera::UnknownImageFormat) + return; + + m_camera->setPreviewFormat(format); +} + +void QAndroidCameraSession::setPreviewCallback(PreviewCallback *callback) +{ + m_videoProbesMutex.lock(); + m_previewCallback = callback; + if (m_camera) + m_camera->notifyNewFrames(m_videoProbes.count() || m_previewCallback); + m_videoProbesMutex.unlock(); +} + +void QAndroidCameraSession::applyImageSettings() +{ + if (!m_camera) + return; + + if (m_actualImageSettings.codec().isEmpty()) + m_actualImageSettings.setCodec(QLatin1String("jpeg")); + + const QSize requestedResolution = m_requestedImageSettings.resolution(); + const QList<QSize> supportedResolutions = m_camera->getSupportedPictureSizes(); + if (!requestedResolution.isValid()) { + // if the viewfinder resolution is explicitly set, pick the highest available capture + // resolution with the same aspect ratio + if (m_requestedViewfinderSettings.resolution().isValid()) { + const QSize vfResolution = m_actualViewfinderSettings.resolution(); + const qreal vfAspectRatio = qreal(vfResolution.width()) / vfResolution.height(); + + auto it = supportedResolutions.rbegin(), end = supportedResolutions.rend(); + for (; it != end; ++it) { + if (qAbs(vfAspectRatio - (qreal(it->width()) / it->height())) < 0.01) { + m_actualImageSettings.setResolution(*it); + break; + } + } + } else { + // otherwise, use the highest supported one + m_actualImageSettings.setResolution(supportedResolutions.last()); + } + } else if (!supportedResolutions.contains(requestedResolution)) { + // if the requested resolution is not supported, find the closest one + int reqPixelCount = requestedResolution.width() * requestedResolution.height(); + QList<int> supportedPixelCounts; + for (int i = 0; i < supportedResolutions.size(); ++i) { + const QSize &s = supportedResolutions.at(i); + supportedPixelCounts.append(s.width() * s.height()); + } + int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount); + m_actualImageSettings.setResolution(supportedResolutions.at(closestIndex)); + } + m_camera->setPictureSize(m_actualImageSettings.resolution()); + + int jpegQuality = 100; + switch (m_requestedImageSettings.quality()) { + case QMultimedia::VeryLowQuality: + jpegQuality = 20; + break; + case QMultimedia::LowQuality: + jpegQuality = 40; + break; + case QMultimedia::NormalQuality: + jpegQuality = 60; + break; + case QMultimedia::HighQuality: + jpegQuality = 80; + break; + case QMultimedia::VeryHighQuality: + jpegQuality = 100; + break; + } + m_camera->setJpegQuality(jpegQuality); +} + +bool QAndroidCameraSession::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const +{ + return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer); +} + +QCameraImageCapture::CaptureDestinations QAndroidCameraSession::captureDestination() const +{ + return m_captureDestination; +} + +void QAndroidCameraSession::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + m_captureDestination = destination; +} + +bool QAndroidCameraSession::isReadyForCapture() const +{ + return m_status == QCamera::ActiveStatus && m_readyForCapture; +} + +void QAndroidCameraSession::setReadyForCapture(bool ready) +{ + if (m_readyForCapture == ready) + return; + + m_readyForCapture = ready; + emit readyForCaptureChanged(ready); +} + +QCameraImageCapture::DriveMode QAndroidCameraSession::driveMode() const +{ + return m_captureImageDriveMode; +} + +void QAndroidCameraSession::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_captureImageDriveMode = mode; +} + +int QAndroidCameraSession::capture(const QString &fileName) +{ + ++m_lastImageCaptureId; + + if (!isReadyForCapture()) { + emit imageCaptureError(m_lastImageCaptureId, QCameraImageCapture::NotReadyError, + tr("Camera not ready")); + return m_lastImageCaptureId; + } + + if (m_captureImageDriveMode == QCameraImageCapture::SingleImageCapture) { + setReadyForCapture(false); + + m_currentImageCaptureId = m_lastImageCaptureId; + m_currentImageCaptureFileName = fileName; + + applyImageSettings(); + applyViewfinderSettings(m_actualImageSettings.resolution()); + + // adjust picture rotation depending on the device orientation + m_camera->setRotation(currentCameraRotation()); + + m_camera->takePicture(); + } else { + //: Drive mode is the camera's shutter mode, for example single shot, continuos exposure, etc. + emit imageCaptureError(m_lastImageCaptureId, QCameraImageCapture::NotSupportedFeatureError, + tr("Drive mode not supported")); + } + + return m_lastImageCaptureId; +} + +void QAndroidCameraSession::cancelCapture() +{ + if (m_readyForCapture) + return; + + m_captureCanceled = true; +} + +void QAndroidCameraSession::onCameraTakePictureFailed() +{ + emit imageCaptureError(m_currentImageCaptureId, QCameraImageCapture::ResourceError, + tr("Failed to capture image")); + + // Preview needs to be restarted and the preview call back must be setup again + m_camera->startPreview(); +} + +void QAndroidCameraSession::onCameraPictureExposed() +{ + if (m_captureCanceled || !m_camera) + return; + + emit imageExposed(m_currentImageCaptureId); + m_camera->fetchLastPreviewFrame(); +} + +void QAndroidCameraSession::onLastPreviewFrameFetched(const QVideoFrame &frame) +{ + if (m_captureCanceled || !m_camera) + return; + + QtConcurrent::run(&QAndroidCameraSession::processPreviewImage, this, + m_currentImageCaptureId, + frame, + m_camera->getRotation()); +} + +void QAndroidCameraSession::processPreviewImage(int id, const QVideoFrame &frame, int rotation) +{ + // Preview display of front-facing cameras is flipped horizontally, but the frame data + // we get here is not. Flip it ourselves if the camera is front-facing to match what the user + // sees on the viewfinder. + QTransform transform; + if (m_camera->getFacing() == AndroidCamera::CameraFacingFront) + transform.scale(-1, 1); + transform.rotate(rotation); + + emit imageCaptured(id, frame.image().transformed(transform)); +} + +void QAndroidCameraSession::onNewPreviewFrame(const QVideoFrame &frame) +{ + if (!m_camera) + return; + + m_videoProbesMutex.lock(); + + for (QAndroidMediaVideoProbeControl *probe : qAsConst(m_videoProbes)) + probe->newFrameProbed(frame); + + if (m_previewCallback) + m_previewCallback->onFrameAvailable(frame); + + m_videoProbesMutex.unlock(); +} + +void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data) +{ + if (!m_captureCanceled) { + // Loading and saving the captured image can be slow, do it in a separate thread + QtConcurrent::run(&QAndroidCameraSession::processCapturedImage, this, + m_currentImageCaptureId, + data, + m_actualImageSettings.resolution(), + m_captureDestination, + m_currentImageCaptureFileName); + } + + m_captureCanceled = false; + + // Preview needs to be restarted after taking a picture + if (m_camera) + m_camera->startPreview(); +} + +void QAndroidCameraSession::onCameraPreviewStarted() +{ + if (m_status == QCamera::StartingStatus) { + m_status = QCamera::ActiveStatus; + emit statusChanged(m_status); + } + + setReadyForCapture(true); +} + +void QAndroidCameraSession::onCameraPreviewFailedToStart() +{ + if (m_status == QCamera::StartingStatus) { + Q_EMIT error(QCamera::CameraError, tr("Camera preview failed to start.")); + + AndroidMultimediaUtils::enableOrientationListener(false); + m_camera->setPreviewSize(QSize()); + m_camera->setPreviewTexture(0); + if (m_videoOutput) { + m_videoOutput->stop(); + m_videoOutput->reset(); + } + m_previewStarted = false; + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + setReadyForCapture(false); + } +} + +void QAndroidCameraSession::onCameraPreviewStopped() +{ + if (m_status == QCamera::StoppingStatus) { + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + } + + setReadyForCapture(false); +} + +void QAndroidCameraSession::processCapturedImage(int id, + const QByteArray &data, + const QSize &resolution, + QCameraImageCapture::CaptureDestinations dest, + const QString &fileName) +{ + + + if (dest & QCameraImageCapture::CaptureToFile) { + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QMediaStorageLocation::Pictures, + QLatin1String("IMG_"), + QLatin1String("jpg")); + + QFile file(actualFileName); + if (file.open(QFile::WriteOnly)) { + if (file.write(data) == data.size()) { + // if the picture is saved into the standard picture location, register it + // with the Android media scanner so it appears immediately in apps + // such as the gallery. + QString standardLoc = AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::DCIM); + if (actualFileName.startsWith(standardLoc)) + AndroidMultimediaUtils::registerMediaFile(actualFileName); + + emit imageSaved(id, actualFileName); + } else { + emit imageCaptureError(id, QCameraImageCapture::OutOfSpaceError, file.errorString()); + } + } else { + const QString errorMessage = tr("Could not open destination file: %1").arg(actualFileName); + emit imageCaptureError(id, QCameraImageCapture::ResourceError, errorMessage); + } + } + + if (dest & QCameraImageCapture::CaptureToBuffer) { + QVideoFrame frame(new QMemoryVideoBuffer(data, -1), resolution, QVideoFrame::Format_Jpeg); + emit imageAvailable(id, frame); + } +} + +QVideoFrame::PixelFormat QAndroidCameraSession::QtPixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat format) +{ + switch (format) { + case AndroidCamera::RGB565: + return QVideoFrame::Format_RGB565; + case AndroidCamera::NV21: + return QVideoFrame::Format_NV21; + case AndroidCamera::YUY2: + return QVideoFrame::Format_YUYV; + case AndroidCamera::JPEG: + return QVideoFrame::Format_Jpeg; + case AndroidCamera::YV12: + return QVideoFrame::Format_YV12; + default: + return QVideoFrame::Format_Invalid; + } +} + +AndroidCamera::ImageFormat QAndroidCameraSession::AndroidImageFormatFromQtPixelFormat(QVideoFrame::PixelFormat format) +{ + switch (format) { + case QVideoFrame::Format_RGB565: + return AndroidCamera::RGB565; + case QVideoFrame::Format_NV21: + return AndroidCamera::NV21; + case QVideoFrame::Format_YUYV: + return AndroidCamera::YUY2; + case QVideoFrame::Format_Jpeg: + return AndroidCamera::JPEG; + case QVideoFrame::Format_YV12: + return AndroidCamera::YV12; + default: + return AndroidCamera::UnknownImageFormat; + } +} + +void QAndroidCameraSession::onVideoOutputReady(bool ready) +{ + if (ready && m_state == QCamera::ActiveState) + startPreview(); +} + +void QAndroidCameraSession::onApplicationStateChanged(Qt::ApplicationState state) +{ + switch (state) { + case Qt::ApplicationInactive: + if (!m_keepActive && m_state != QCamera::UnloadedState) { + m_savedState = m_state; + close(); + m_state = QCamera::UnloadedState; + emit stateChanged(m_state); + } + break; + case Qt::ApplicationActive: + if (m_savedState != -1) { + setStateHelper(QCamera::State(m_savedState)); + m_savedState = -1; + } + break; + default: + break; + } +} + +bool QAndroidCameraSession::requestRecordingPermission() +{ + m_keepActive = true; + const bool result = qt_androidRequestRecordingPermission(); + m_keepActive = false; + return result; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcamerasession_p.h b/src/multimedia/platform/android/mediacapture/qandroidcamerasession_p.h new file mode 100644 index 000000000..5d4c06738 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcamerasession_p.h @@ -0,0 +1,216 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 QANDROIDCAMERASESSION_H +#define QANDROIDCAMERASESSION_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 <qcamera.h> +#include <qmediaencodersettings.h> +#include <QCameraImageCapture> +#include <QSet> +#include <QMutex> +#include <private/qmediastoragelocation_p.h> +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +class QAndroidVideoOutput; +class QAndroidMediaVideoProbeControl; + +class QAndroidCameraSession : public QObject +{ + Q_OBJECT +public: + explicit QAndroidCameraSession(QObject *parent = 0); + ~QAndroidCameraSession(); + + static const QList<AndroidCameraInfo> &availableCameras(); + + void setSelectedCamera(int cameraId) { m_selectedCamera = cameraId; } + AndroidCamera *camera() const { return m_camera; } + + QCamera::State state() const { return m_state; } + void setState(QCamera::State state); + + QCamera::Status status() const { return m_status; } + + QCamera::CaptureModes captureMode() const { return m_captureMode; } + void setCaptureMode(QCamera::CaptureModes mode); + bool isCaptureModeSupported(QCamera::CaptureModes mode) const; + + QCameraViewfinderSettings viewfinderSettings() const { return m_actualViewfinderSettings; } + void setViewfinderSettings(const QCameraViewfinderSettings &settings); + void applyViewfinderSettings(const QSize &captureSize = QSize(), bool restartPreview = true); + + QAndroidVideoOutput *videoOutput() const { return m_videoOutput; } + void setVideoOutput(QAndroidVideoOutput *output); + + QList<QSize> getSupportedPreviewSizes() const; + QList<QVideoFrame::PixelFormat> getSupportedPixelFormats() const; + QList<AndroidCamera::FpsRange> getSupportedPreviewFpsRange() const; + + QImageEncoderSettings imageSettings() const { return m_actualImageSettings; } + void setImageSettings(const QImageEncoderSettings &settings); + + bool isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const; + QCameraImageCapture::CaptureDestinations captureDestination() const; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination); + + bool isReadyForCapture() const; + void setReadyForCapture(bool ready); + QCameraImageCapture::DriveMode driveMode() const; + void setDriveMode(QCameraImageCapture::DriveMode mode); + int capture(const QString &fileName); + void cancelCapture(); + + int currentCameraRotation() const; + + void addProbe(QAndroidMediaVideoProbeControl *probe); + void removeProbe(QAndroidMediaVideoProbeControl *probe); + + void setPreviewFormat(AndroidCamera::ImageFormat format); + + struct PreviewCallback + { + virtual void onFrameAvailable(const QVideoFrame &frame) = 0; + }; + void setPreviewCallback(PreviewCallback *callback); + bool requestRecordingPermission(); + +Q_SIGNALS: + void statusChanged(QCamera::Status status); + void stateChanged(QCamera::State); + void error(int error, const QString &errorString); + void captureModeChanged(QCamera::CaptureModes); + void opened(); + + void captureDestinationChanged(QCameraImageCapture::CaptureDestinations destination); + + void readyForCaptureChanged(bool); + void imageExposed(int id); + void imageCaptured(int id, const QImage &preview); + void imageMetadataAvailable(int id, const QString &key, const QVariant &value); + void imageAvailable(int id, const QVideoFrame &buffer); + void imageSaved(int id, const QString &fileName); + void imageCaptureError(int id, int error, const QString &errorString); + +private Q_SLOTS: + void onVideoOutputReady(bool ready); + + void onApplicationStateChanged(Qt::ApplicationState state); + + void onCameraTakePictureFailed(); + void onCameraPictureExposed(); + void onCameraPictureCaptured(const QByteArray &data); + void onLastPreviewFrameFetched(const QVideoFrame &frame); + void onNewPreviewFrame(const QVideoFrame &frame); + void onCameraPreviewStarted(); + void onCameraPreviewFailedToStart(); + void onCameraPreviewStopped(); + +private: + static void updateAvailableCameras(); + + bool open(); + void close(); + + bool startPreview(); + void stopPreview(); + + void applyImageSettings(); + + void processPreviewImage(int id, const QVideoFrame &frame, int rotation); + void processCapturedImage(int id, + const QByteArray &data, + const QSize &resolution, + QCameraImageCapture::CaptureDestinations dest, + const QString &fileName); + + static QVideoFrame::PixelFormat QtPixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat); + static AndroidCamera::ImageFormat AndroidImageFormatFromQtPixelFormat(QVideoFrame::PixelFormat); + + void setStateHelper(QCamera::State state); + + int m_selectedCamera; + AndroidCamera *m_camera; + int m_nativeOrientation; + QAndroidVideoOutput *m_videoOutput; + + QCamera::CaptureModes m_captureMode; + QCamera::State m_state; + int m_savedState; + QCamera::Status m_status; + bool m_previewStarted; + + QCameraViewfinderSettings m_requestedViewfinderSettings; + QCameraViewfinderSettings m_actualViewfinderSettings; + + QImageEncoderSettings m_requestedImageSettings; + QImageEncoderSettings m_actualImageSettings; + QCameraImageCapture::CaptureDestinations m_captureDestination; + QCameraImageCapture::DriveMode m_captureImageDriveMode; + int m_lastImageCaptureId; + bool m_readyForCapture; + bool m_captureCanceled; + int m_currentImageCaptureId; + QString m_currentImageCaptureFileName; + + QMediaStorageLocation m_mediaStorageLocation; + + QSet<QAndroidMediaVideoProbeControl *> m_videoProbes; + QMutex m_videoProbesMutex; + PreviewCallback *m_previewCallback; + bool m_keepActive; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERASESSION_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol.cpp new file mode 100644 index 000000000..6084ed43f --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol.cpp @@ -0,0 +1,281 @@ +/**************************************************************************** +** +** 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 "qandroidcameravideorenderercontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "qandroidvideooutput_p.h" +#include "androidsurfaceview_p.h" +#include "qandroidmultimediautils_p.h" +#include <qabstractvideosurface.h> +#include <qvideosurfaceformat.h> +#include <qcoreapplication.h> +#include <qthread.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraDataVideoOutput : public QAndroidVideoOutput + , public QAndroidCameraSession::PreviewCallback +{ + Q_OBJECT +public: + explicit QAndroidCameraDataVideoOutput(QAndroidCameraVideoRendererControl *control); + ~QAndroidCameraDataVideoOutput() override; + + AndroidSurfaceHolder *surfaceHolder() override; + + bool isReady() override; + + void stop() override; + +private Q_SLOTS: + void onSurfaceCreated(); + void configureFormat(); + +private: + void onFrameAvailable(const QVideoFrame &frame); + void presentFrame(); + bool event(QEvent *); + + QAndroidCameraVideoRendererControl *m_control; + AndroidSurfaceView *m_surfaceView; + QMutex m_mutex; + QVideoFrame::PixelFormat m_pixelFormat; + QVideoFrame m_lastFrame; +}; + +QAndroidCameraDataVideoOutput::QAndroidCameraDataVideoOutput(QAndroidCameraVideoRendererControl *control) + : QAndroidVideoOutput(control) + , m_control(control) + , m_pixelFormat(QVideoFrame::Format_Invalid) +{ + // The camera preview cannot be started unless we set a SurfaceTexture or a + // SurfaceHolder. In this case we don't actually care about either of these, but since + // we need to, we setup an offscreen dummy SurfaceView in order to be able to start + // the camera preview. We'll then be able to use setPreviewCallbackWithBuffer() to + // get the raw data. + + m_surfaceView = new AndroidSurfaceView; + + connect(m_surfaceView, &AndroidSurfaceView::surfaceCreated, + this, &QAndroidCameraDataVideoOutput::onSurfaceCreated); + + m_surfaceView->setGeometry(-1, -1, 1, 1); + m_surfaceView->setVisible(true); + + connect(m_control->cameraSession(), &QAndroidCameraSession::opened, + this, &QAndroidCameraDataVideoOutput::configureFormat); + connect(m_control->surface(), &QAbstractVideoSurface::supportedFormatsChanged, + this, &QAndroidCameraDataVideoOutput::configureFormat); + configureFormat(); +} + +QAndroidCameraDataVideoOutput::~QAndroidCameraDataVideoOutput() +{ + m_control->cameraSession()->setPreviewCallback(nullptr); + delete m_surfaceView; +} + +AndroidSurfaceHolder *QAndroidCameraDataVideoOutput::surfaceHolder() +{ + return m_surfaceView->holder(); +} + +bool QAndroidCameraDataVideoOutput::isReady() +{ + return m_surfaceView->holder() && m_surfaceView->holder()->isSurfaceCreated(); +} + +void QAndroidCameraDataVideoOutput::onSurfaceCreated() +{ + emit readyChanged(true); +} + +void QAndroidCameraDataVideoOutput::configureFormat() +{ + m_pixelFormat = QVideoFrame::Format_Invalid; + + if (!m_control->cameraSession()->camera()) + return; + + QList<QVideoFrame::PixelFormat> surfaceFormats = m_control->surface()->supportedPixelFormats(); + QList<AndroidCamera::ImageFormat> previewFormats = m_control->cameraSession()->camera()->getSupportedPreviewFormats(); + for (int i = 0; i < surfaceFormats.size(); ++i) { + QVideoFrame::PixelFormat pixFormat = surfaceFormats.at(i); + AndroidCamera::ImageFormat f = qt_androidImageFormatFromPixelFormat(pixFormat); + if (previewFormats.contains(f)) { + m_pixelFormat = pixFormat; + break; + } + } + + if (m_pixelFormat == QVideoFrame::Format_Invalid) { + m_control->cameraSession()->setPreviewCallback(nullptr); + qWarning("The video surface is not compatible with any format supported by the camera"); + } else { + m_control->cameraSession()->setPreviewCallback(this); + + if (m_control->cameraSession()->status() > QCamera::LoadedStatus) + m_control->cameraSession()->camera()->stopPreview(); + + m_control->cameraSession()->setPreviewFormat(qt_androidImageFormatFromPixelFormat(m_pixelFormat)); + + if (m_control->cameraSession()->status() > QCamera::LoadedStatus) + m_control->cameraSession()->camera()->startPreview(); + } +} + +void QAndroidCameraDataVideoOutput::stop() +{ + m_mutex.lock(); + m_lastFrame = QVideoFrame(); + m_mutex.unlock(); + + if (m_control->surface() && m_control->surface()->isActive()) + m_control->surface()->stop(); +} + +void QAndroidCameraDataVideoOutput::onFrameAvailable(const QVideoFrame &frame) +{ + m_mutex.lock(); + m_lastFrame = frame; + m_mutex.unlock(); + + if (thread() == QThread::currentThread()) + presentFrame(); + else + QCoreApplication::postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority); +} + +bool QAndroidCameraDataVideoOutput::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + presentFrame(); + return true; + } + + return QObject::event(e); +} + +void QAndroidCameraDataVideoOutput::presentFrame() +{ + Q_ASSERT(thread() == QThread::currentThread()); + + QMutexLocker locker(&m_mutex); + + if (m_control->surface() && m_lastFrame.isValid() && m_lastFrame.pixelFormat() == m_pixelFormat) { + + if (m_control->surface()->isActive() && (m_control->surface()->surfaceFormat().pixelFormat() != m_lastFrame.pixelFormat() + || m_control->surface()->surfaceFormat().frameSize() != m_lastFrame.size())) { + m_control->surface()->stop(); + } + + if (!m_control->surface()->isActive()) { + QVideoSurfaceFormat format(m_lastFrame.size(), m_lastFrame.pixelFormat(), m_lastFrame.handleType()); + // Front camera frames are automatically mirrored when using SurfaceTexture or SurfaceView, + // but the buffers we get from the data callback are not. Tell the QAbstractVideoSurface + // that it needs to mirror the frames. + if (m_control->cameraSession()->camera()->getFacing() == AndroidCamera::CameraFacingFront) + format.setProperty("mirrored", true); + + m_control->surface()->start(format); + } + + if (m_control->surface()->isActive()) + m_control->surface()->present(m_lastFrame); + } + + m_lastFrame = QVideoFrame(); +} + + +QAndroidCameraVideoRendererControl::QAndroidCameraVideoRendererControl(QAndroidCameraSession *session, QObject *parent) + : QVideoRendererControl(parent) + , m_cameraSession(session) + , m_surface(0) + , m_textureOutput(0) + , m_dataOutput(0) +{ +} + +QAndroidCameraVideoRendererControl::~QAndroidCameraVideoRendererControl() +{ + m_cameraSession->setVideoOutput(0); +} + +QAbstractVideoSurface *QAndroidCameraVideoRendererControl::surface() const +{ + return m_surface; +} + +void QAndroidCameraVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface == surface) + return; + + m_surface = surface; + QAndroidVideoOutput *oldOutput = m_textureOutput ? static_cast<QAndroidVideoOutput*>(m_textureOutput) + : static_cast<QAndroidVideoOutput*>(m_dataOutput); + QAndroidVideoOutput *newOutput = 0; + + if (m_surface) { + if (!m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).isEmpty()) { + if (!m_textureOutput) { + m_dataOutput = 0; + newOutput = m_textureOutput = new QAndroidTextureVideoOutput(this); + } + } else if (!m_dataOutput) { + m_textureOutput = 0; + newOutput = m_dataOutput = new QAndroidCameraDataVideoOutput(this); + } + + if (m_textureOutput) + m_textureOutput->setSurface(m_surface); + } + + if (newOutput != oldOutput) { + m_cameraSession->setVideoOutput(newOutput); + delete oldOutput; + } +} + +QT_END_NAMESPACE + +#include "qandroidcameravideorenderercontrol.moc" + diff --git a/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol_p.h new file mode 100644 index 000000000..0deaac943 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcameravideorenderercontrol_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QANDROIDCAMERAVIDEORENDERERCONTROL_H +#define QANDROIDCAMERAVIDEORENDERERCONTROL_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 <qvideorenderercontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; +class QAndroidTextureVideoOutput; +class QAndroidCameraDataVideoOutput; + +class QAndroidCameraVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + QAndroidCameraVideoRendererControl(QAndroidCameraSession *session, QObject *parent = 0); + ~QAndroidCameraVideoRendererControl() override; + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + + QAndroidCameraSession *cameraSession() const { return m_cameraSession; } + +private: + QAndroidCameraSession *m_cameraSession; + QAbstractVideoSurface *m_surface; + QAndroidTextureVideoOutput *m_textureOutput; + QAndroidCameraDataVideoOutput *m_dataOutput; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAMERAVIDEORENDERERCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcaptureservice.cpp b/src/multimedia/platform/android/mediacapture/qandroidcaptureservice.cpp new file mode 100644 index 000000000..79808956e --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcaptureservice.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 "qandroidcaptureservice_p.h" + +#include "qandroidmediarecordercontrol_p.h" +#include "qandroidcapturesession_p.h" +#include "qandroidcameracontrol_p.h" +#include "qandroidvideodeviceselectorcontrol_p.h" +#include "qandroidaudioinputselectorcontrol_p.h" +#include "qandroidcamerasession_p.h" +#include "qandroidcameravideorenderercontrol_p.h" +#include "qandroidcameraexposurecontrol_p.h" +#include "qandroidcamerafocuscontrol_p.h" +#include "qandroidcameraimageprocessingcontrol_p.h" +#include "qandroidimageencodercontrol_p.h" +#include "qandroidcameraimagecapturecontrol_p.h" +#include "qandroidaudioencodersettingscontrol_p.h" +#include "qandroidvideoencodersettingscontrol_p.h" +#include "qandroidmediacontainercontrol_p.h" +#include "qandroidmediavideoprobecontrol_p.h" + +#include <qmediaserviceproviderplugin.h> + +QT_BEGIN_NAMESPACE + +QAndroidCaptureService::QAndroidCaptureService(const QString &service, QObject *parent) + : QMediaService(parent) + , m_service(service) + , m_videoRendererControl(0) +{ + if (m_service == QLatin1String(Q_MEDIASERVICE_CAMERA)) { + m_cameraSession = new QAndroidCameraSession; + m_cameraControl = new QAndroidCameraControl(m_cameraSession); + m_videoInputControl = new QAndroidVideoDeviceSelectorControl(m_cameraSession); + m_cameraExposureControl = new QAndroidCameraExposureControl(m_cameraSession); + m_cameraFocusControl = new QAndroidCameraFocusControl(m_cameraSession); + m_cameraImageProcessingControl = new QAndroidCameraImageProcessingControl(m_cameraSession); + m_imageEncoderControl = new QAndroidImageEncoderControl(m_cameraSession); + m_imageCaptureControl = new QAndroidCameraImageCaptureControl(m_cameraSession); + m_audioInputControl = 0; + } else { + m_cameraSession = 0; + m_cameraControl = 0; + m_videoInputControl = 0; + m_cameraExposureControl = 0; + m_cameraFocusControl = 0; + m_cameraImageProcessingControl = 0; + m_imageEncoderControl = 0; + m_imageCaptureControl = 0; + m_videoEncoderSettingsControl = 0; + } + + m_captureSession = new QAndroidCaptureSession(m_cameraSession); + m_recorderControl = new QAndroidMediaRecorderControl(m_captureSession); + m_audioEncoderSettingsControl = new QAndroidAudioEncoderSettingsControl(m_captureSession); + m_mediaContainerControl = new QAndroidMediaContainerControl(m_captureSession); + + if (m_service == QLatin1String(Q_MEDIASERVICE_CAMERA)) { + m_videoEncoderSettingsControl = new QAndroidVideoEncoderSettingsControl(m_captureSession); + } else { + m_audioInputControl = new QAndroidAudioInputSelectorControl(m_captureSession); + m_captureSession->setAudioInput(m_audioInputControl->defaultInput()); + } +} + +QAndroidCaptureService::~QAndroidCaptureService() +{ + delete m_audioEncoderSettingsControl; + delete m_videoEncoderSettingsControl; + delete m_mediaContainerControl; + delete m_recorderControl; + delete m_captureSession; + delete m_cameraControl; + delete m_audioInputControl; + delete m_videoInputControl; + delete m_videoRendererControl; + delete m_cameraExposureControl; + delete m_cameraFocusControl; + delete m_cameraImageProcessingControl; + delete m_imageEncoderControl; + delete m_imageCaptureControl; + delete m_cameraSession; +} + +QObject *QAndroidCaptureService::requestControl(const char *name) +{ + if (qstrcmp(name, QMediaRecorderControl_iid) == 0) + return m_recorderControl; + + if (qstrcmp(name, QMediaContainerControl_iid) == 0) + return m_mediaContainerControl; + + if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0) + return m_audioEncoderSettingsControl; + + if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) + return m_videoEncoderSettingsControl; + + if (qstrcmp(name, QCameraControl_iid) == 0) + return m_cameraControl; + + if (qstrcmp(name, QAudioInputSelectorControl_iid) == 0) + return m_audioInputControl; + + if (qstrcmp(name, QVideoDeviceSelectorControl_iid) == 0) + return m_videoInputControl; + + if (qstrcmp(name, QCameraExposureControl_iid) == 0) + return m_cameraExposureControl; + + if (qstrcmp(name, QCameraFocusControl_iid) == 0) + return m_cameraFocusControl; + + if (qstrcmp(name, QCameraImageProcessingControl_iid) == 0) + return m_cameraImageProcessingControl; + + if (qstrcmp(name, QImageEncoderControl_iid) == 0) + return m_imageEncoderControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + + if (qstrcmp(name, QVideoRendererControl_iid) == 0 + && m_service == QLatin1String(Q_MEDIASERVICE_CAMERA) + && !m_videoRendererControl) { + m_videoRendererControl = new QAndroidCameraVideoRendererControl(m_cameraSession); + return m_videoRendererControl; + } + + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { + QAndroidMediaVideoProbeControl *videoProbe = 0; + if (m_cameraSession) { + videoProbe = new QAndroidMediaVideoProbeControl(this); + m_cameraSession->addProbe(videoProbe); + } + return videoProbe; + } + + return 0; +} + +void QAndroidCaptureService::releaseControl(QObject *control) +{ + if (control) { + if (control == m_videoRendererControl) { + delete m_videoRendererControl; + m_videoRendererControl = 0; + return; + } + + QAndroidMediaVideoProbeControl *videoProbe = qobject_cast<QAndroidMediaVideoProbeControl *>(control); + if (videoProbe) { + if (m_cameraSession) + m_cameraSession->removeProbe(videoProbe); + delete videoProbe; + return; + } + } + +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcaptureservice_p.h b/src/multimedia/platform/android/mediacapture/qandroidcaptureservice_p.h new file mode 100644 index 000000000..e202c40c2 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcaptureservice_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 QANDROIDCAPTURESERVICE_H +#define QANDROIDCAPTURESERVICE_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 <qmediaservice.h> + +QT_BEGIN_NAMESPACE + +class QAndroidMediaRecorderControl; +class QAndroidCaptureSession; +class QAndroidCameraControl; +class QAndroidVideoDeviceSelectorControl; +class QAndroidAudioInputSelectorControl; +class QAndroidCameraSession; +class QAndroidCameraVideoRendererControl; +class QAndroidCameraExposureControl; +class QAndroidCameraFocusControl; +class QAndroidCameraImageProcessingControl; +class QAndroidImageEncoderControl; +class QAndroidCameraImageCaptureControl; +class QAndroidAudioEncoderSettingsControl; +class QAndroidVideoEncoderSettingsControl; +class QAndroidMediaContainerControl; + +class QAndroidCaptureService : public QMediaService +{ + Q_OBJECT + +public: + explicit QAndroidCaptureService(const QString &service, QObject *parent = 0); + virtual ~QAndroidCaptureService(); + + QObject *requestControl(const char *name); + void releaseControl(QObject *); + +private: + QString m_service; + + QAndroidMediaRecorderControl *m_recorderControl; + QAndroidCaptureSession *m_captureSession; + QAndroidCameraControl *m_cameraControl; + QAndroidVideoDeviceSelectorControl *m_videoInputControl; + QAndroidAudioInputSelectorControl *m_audioInputControl; + QAndroidCameraSession *m_cameraSession; + QAndroidCameraVideoRendererControl *m_videoRendererControl; + QAndroidCameraExposureControl *m_cameraExposureControl; + QAndroidCameraFocusControl *m_cameraFocusControl; + QAndroidCameraImageProcessingControl *m_cameraImageProcessingControl; + QAndroidImageEncoderControl *m_imageEncoderControl; + QAndroidCameraImageCaptureControl *m_imageCaptureControl; + QAndroidAudioEncoderSettingsControl *m_audioEncoderSettingsControl; + QAndroidVideoEncoderSettingsControl *m_videoEncoderSettingsControl; + QAndroidMediaContainerControl *m_mediaContainerControl; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAPTURESERVICE_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp new file mode 100644 index 000000000..67f748994 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession.cpp @@ -0,0 +1,594 @@ +/**************************************************************************** +** +** 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 "qandroidcapturesession_p.h" + +#include "androidcamera_p.h" +#include "qandroidcamerasession_p.h" +#include "androidmultimediautils_p.h" +#include "qandroidmultimediautils_p.h" +#include "qandroidvideooutput_p.h" +#include "qandroidglobal_p.h" + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +QAndroidCaptureSession::QAndroidCaptureSession(QAndroidCameraSession *cameraSession) + : QObject() + , m_mediaRecorder(0) + , m_cameraSession(cameraSession) + , m_audioSource(AndroidMediaRecorder::DefaultAudioSource) + , m_duration(0) + , m_state(QMediaRecorder::StoppedState) + , m_status(QMediaRecorder::UnloadedStatus) + , m_containerFormatDirty(true) + , m_videoSettingsDirty(true) + , m_audioSettingsDirty(true) + , m_outputFormat(AndroidMediaRecorder::DefaultOutputFormat) + , m_audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder) + , m_videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder) +{ + m_mediaStorageLocation.addStorageLocation( + QMediaStorageLocation::Movies, + AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::DCIM)); + + m_mediaStorageLocation.addStorageLocation( + QMediaStorageLocation::Sounds, + AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::Sounds)); + + if (cameraSession) { + connect(cameraSession, SIGNAL(opened()), this, SLOT(onCameraOpened())); + connect(cameraSession, &QAndroidCameraSession::statusChanged, this, + [this](QCamera::Status status) { + if (status == QCamera::UnavailableStatus) { + setState(QMediaRecorder::StoppedState); + setStatus(QMediaRecorder::UnavailableStatus); + return; + } + + // Stop recording when stopping the camera. + if (status == QCamera::StoppingStatus) { + setState(QMediaRecorder::StoppedState); + setStatus(QMediaRecorder::UnloadedStatus); + return; + } + + if (status == QCamera::LoadingStatus) + setStatus(QMediaRecorder::LoadingStatus); + }); + connect(cameraSession, &QAndroidCameraSession::captureModeChanged, this, + [this](QCamera::CaptureModes mode) { + if (!mode.testFlag(QCamera::CaptureVideo)) { + setState(QMediaRecorder::StoppedState); + setStatus(QMediaRecorder::UnloadedStatus); + } + }); + connect(cameraSession, &QAndroidCameraSession::readyForCaptureChanged, this, + [this](bool ready) { + if (ready) + setStatus(QMediaRecorder::LoadedStatus); + }); + } else { + // Audio-only recording. + setStatus(QMediaRecorder::LoadedStatus); + } + + m_notifyTimer.setInterval(1000); + connect(&m_notifyTimer, SIGNAL(timeout()), this, SLOT(updateDuration())); +} + +QAndroidCaptureSession::~QAndroidCaptureSession() +{ + stop(); + delete m_mediaRecorder; +} + +void QAndroidCaptureSession::setAudioInput(const QString &input) +{ + if (m_audioInput == input) + return; + + m_audioInput = input; + + if (m_audioInput == QLatin1String("default")) + m_audioSource = AndroidMediaRecorder::DefaultAudioSource; + else if (m_audioInput == QLatin1String("mic")) + m_audioSource = AndroidMediaRecorder::Mic; + else if (m_audioInput == QLatin1String("voice_uplink")) + m_audioSource = AndroidMediaRecorder::VoiceUplink; + else if (m_audioInput == QLatin1String("voice_downlink")) + m_audioSource = AndroidMediaRecorder::VoiceDownlink; + else if (m_audioInput == QLatin1String("voice_call")) + m_audioSource = AndroidMediaRecorder::VoiceCall; + else if (m_audioInput == QLatin1String("voice_recognition")) + m_audioSource = AndroidMediaRecorder::VoiceRecognition; + else + m_audioSource = AndroidMediaRecorder::DefaultAudioSource; + + emit audioInputChanged(m_audioInput); +} + +QUrl QAndroidCaptureSession::outputLocation() const +{ + return m_actualOutputLocation; +} + +bool QAndroidCaptureSession::setOutputLocation(const QUrl &location) +{ + if (m_requestedOutputLocation == location) + return false; + + m_actualOutputLocation = QUrl(); + m_requestedOutputLocation = location; + + if (m_requestedOutputLocation.isEmpty()) + return true; + + if (m_requestedOutputLocation.isValid() + && (m_requestedOutputLocation.isLocalFile() || m_requestedOutputLocation.isRelative())) { + return true; + } + + m_requestedOutputLocation = QUrl(); + return false; +} + +QMediaRecorder::State QAndroidCaptureSession::state() const +{ + return m_state; +} + +void QAndroidCaptureSession::setState(QMediaRecorder::State state) +{ + if (m_state == state) + return; + + switch (state) { + case QMediaRecorder::StoppedState: + stop(); + break; + case QMediaRecorder::RecordingState: + start(); + break; + case QMediaRecorder::PausedState: + // Not supported by Android API + qWarning("QMediaRecorder::PausedState is not supported on Android"); + break; + } +} + +void QAndroidCaptureSession::start() +{ + if (m_state == QMediaRecorder::RecordingState || m_status != QMediaRecorder::LoadedStatus) + return; + + setStatus(QMediaRecorder::StartingStatus); + + if (m_mediaRecorder) { + m_mediaRecorder->release(); + delete m_mediaRecorder; + } + + const bool granted = m_cameraSession + ? m_cameraSession->requestRecordingPermission() + : qt_androidRequestRecordingPermission(); + if (!granted) { + setStatus(QMediaRecorder::UnavailableStatus); + Q_EMIT error(QMediaRecorder::ResourceError, QLatin1String("Permission denied.")); + return; + } + + m_mediaRecorder = new AndroidMediaRecorder; + connect(m_mediaRecorder, SIGNAL(error(int,int)), this, SLOT(onError(int,int))); + connect(m_mediaRecorder, SIGNAL(info(int,int)), this, SLOT(onInfo(int,int))); + + // Set audio/video sources + if (m_cameraSession) { + updateViewfinder(); + m_cameraSession->camera()->unlock(); + m_mediaRecorder->setCamera(m_cameraSession->camera()); + m_mediaRecorder->setAudioSource(AndroidMediaRecorder::Camcorder); + m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera); + } else { + m_mediaRecorder->setAudioSource(m_audioSource); + } + + // Set output format + m_mediaRecorder->setOutputFormat(m_outputFormat); + + // Set audio encoder settings + m_mediaRecorder->setAudioChannels(m_audioSettings.channelCount()); + m_mediaRecorder->setAudioEncodingBitRate(m_audioSettings.bitRate()); + m_mediaRecorder->setAudioSamplingRate(m_audioSettings.sampleRate()); + m_mediaRecorder->setAudioEncoder(m_audioEncoder); + + // Set video encoder settings + if (m_cameraSession) { + m_mediaRecorder->setVideoSize(m_videoSettings.resolution()); + m_mediaRecorder->setVideoFrameRate(qRound(m_videoSettings.frameRate())); + m_mediaRecorder->setVideoEncodingBitRate(m_videoSettings.bitRate()); + m_mediaRecorder->setVideoEncoder(m_videoEncoder); + + m_mediaRecorder->setOrientationHint(m_cameraSession->currentCameraRotation()); + } + + // Set output file + QString filePath = m_mediaStorageLocation.generateFileName( + m_requestedOutputLocation.isLocalFile() ? m_requestedOutputLocation.toLocalFile() + : m_requestedOutputLocation.toString(), + m_cameraSession ? QMediaStorageLocation::Movies + : QMediaStorageLocation::Sounds, + m_cameraSession ? QLatin1String("VID_") + : QLatin1String("REC_"), + m_containerFormat); + + m_usedOutputLocation = QUrl::fromLocalFile(filePath); + m_mediaRecorder->setOutputFile(filePath); + + // Even though the Android doc explicitly says that calling MediaRecorder.setPreviewDisplay() + // is not necessary when the Camera already has a Surface, it doesn't actually work on some + // devices. For example on the Samsung Galaxy Tab 2, the camera server dies after prepare() + // and start() if MediaRecorder.setPreviewDispaly() is not called. + if (m_cameraSession) { + // When using a SurfaceTexture, we need to pass a new one to the MediaRecorder, not the same + // one that is set on the Camera or it will crash, hence the reset(). + m_cameraSession->videoOutput()->reset(); + if (m_cameraSession->videoOutput()->surfaceTexture()) + m_mediaRecorder->setSurfaceTexture(m_cameraSession->videoOutput()->surfaceTexture()); + else if (m_cameraSession->videoOutput()->surfaceHolder()) + m_mediaRecorder->setSurfaceHolder(m_cameraSession->videoOutput()->surfaceHolder()); + } + + if (!m_mediaRecorder->prepare()) { + emit error(QMediaRecorder::FormatError, QLatin1String("Unable to prepare the media recorder.")); + if (m_cameraSession) + restartViewfinder(); + return; + } + + if (!m_mediaRecorder->start()) { + emit error(QMediaRecorder::FormatError, QLatin1String("Unable to start the media recorder.")); + if (m_cameraSession) + restartViewfinder(); + return; + } + + m_elapsedTime.start(); + m_notifyTimer.start(); + updateDuration(); + + if (m_cameraSession) { + m_cameraSession->setReadyForCapture(false); + + // Preview frame callback is cleared when setting up the camera with the media recorder. + // We need to reset it. + m_cameraSession->camera()->setupPreviewFrameCallback(); + } + + m_state = QMediaRecorder::RecordingState; + emit stateChanged(m_state); + setStatus(QMediaRecorder::RecordingStatus); +} + +void QAndroidCaptureSession::stop(bool error) +{ + if (m_state == QMediaRecorder::StoppedState || m_mediaRecorder == 0) + return; + + setStatus(QMediaRecorder::FinalizingStatus); + + m_mediaRecorder->stop(); + m_notifyTimer.stop(); + updateDuration(); + m_elapsedTime.invalidate(); + m_mediaRecorder->release(); + delete m_mediaRecorder; + m_mediaRecorder = 0; + + if (m_cameraSession && m_cameraSession->status() == QCamera::ActiveStatus) { + // Viewport needs to be restarted after recording + restartViewfinder(); + } + + if (!error) { + // if the media is saved into the standard media location, register it + // with the Android media scanner so it appears immediately in apps + // such as the gallery. + QString mediaPath = m_usedOutputLocation.toLocalFile(); + QString standardLoc = m_cameraSession ? AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::DCIM) + : AndroidMultimediaUtils::getDefaultMediaDirectory(AndroidMultimediaUtils::Sounds); + if (mediaPath.startsWith(standardLoc)) + AndroidMultimediaUtils::registerMediaFile(mediaPath); + + m_actualOutputLocation = m_usedOutputLocation; + emit actualLocationChanged(m_actualOutputLocation); + } + + m_state = QMediaRecorder::StoppedState; + emit stateChanged(m_state); + if (!m_cameraSession) + setStatus(QMediaRecorder::LoadedStatus); +} + +void QAndroidCaptureSession::setStatus(QMediaRecorder::Status status) +{ + if (m_status == status) + return; + + m_status = status; + emit statusChanged(m_status); +} + +QMediaRecorder::Status QAndroidCaptureSession::status() const +{ + return m_status; +} + +qint64 QAndroidCaptureSession::duration() const +{ + return m_duration; +} + +void QAndroidCaptureSession::setContainerFormat(const QString &format) +{ + if (m_containerFormat == format) + return; + + m_containerFormat = format; + m_containerFormatDirty = true; +} + +void QAndroidCaptureSession::setAudioSettings(const QAudioEncoderSettings &settings) +{ + if (m_audioSettings == settings) + return; + + m_audioSettings = settings; + m_audioSettingsDirty = true; +} + +void QAndroidCaptureSession::setVideoSettings(const QVideoEncoderSettings &settings) +{ + if (!m_cameraSession || m_videoSettings == settings) + return; + + m_videoSettings = settings; + m_videoSettingsDirty = true; +} + +void QAndroidCaptureSession::applySettings() +{ + // container settings + if (m_containerFormatDirty) { + if (m_containerFormat.isEmpty()) { + m_containerFormat = m_defaultSettings.outputFileExtension; + m_outputFormat = m_defaultSettings.outputFormat; + } else if (m_containerFormat == QLatin1String("3gp")) { + m_outputFormat = AndroidMediaRecorder::THREE_GPP; + } else if (!m_cameraSession && m_containerFormat == QLatin1String("amr")) { + m_outputFormat = AndroidMediaRecorder::AMR_NB_Format; + } else if (!m_cameraSession && m_containerFormat == QLatin1String("awb")) { + m_outputFormat = AndroidMediaRecorder::AMR_WB_Format; + } else { + m_containerFormat = QStringLiteral("mp4"); + m_outputFormat = AndroidMediaRecorder::MPEG_4; + } + + m_containerFormatDirty = false; + } + + // audio settings + if (m_audioSettingsDirty) { + if (m_audioSettings.channelCount() <= 0) + m_audioSettings.setChannelCount(m_defaultSettings.audioChannels); + if (m_audioSettings.bitRate() <= 0) + m_audioSettings.setBitRate(m_defaultSettings.audioBitRate); + if (m_audioSettings.sampleRate() <= 0) + m_audioSettings.setSampleRate(m_defaultSettings.audioSampleRate); + + if (m_audioSettings.codec().isEmpty()) + m_audioEncoder = m_defaultSettings.audioEncoder; + else if (m_audioSettings.codec() == QLatin1String("aac")) + m_audioEncoder = AndroidMediaRecorder::AAC; + else if (m_audioSettings.codec() == QLatin1String("amr-nb")) + m_audioEncoder = AndroidMediaRecorder::AMR_NB_Encoder; + else if (m_audioSettings.codec() == QLatin1String("amr-wb")) + m_audioEncoder = AndroidMediaRecorder::AMR_WB_Encoder; + else + m_audioEncoder = m_defaultSettings.audioEncoder; + + m_audioSettingsDirty = false; + } + + // video settings + if (m_cameraSession && m_cameraSession->camera() && m_videoSettingsDirty) { + if (m_videoSettings.resolution().isEmpty()) { + m_videoSettings.setResolution(m_defaultSettings.videoResolution); + } else if (!m_supportedResolutions.contains(m_videoSettings.resolution())) { + // if the requested resolution is not supported, find the closest one + QSize reqSize = m_videoSettings.resolution(); + int reqPixelCount = reqSize.width() * reqSize.height(); + QList<int> supportedPixelCounts; + for (int i = 0; i < m_supportedResolutions.size(); ++i) { + const QSize &s = m_supportedResolutions.at(i); + supportedPixelCounts.append(s.width() * s.height()); + } + int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount); + m_videoSettings.setResolution(m_supportedResolutions.at(closestIndex)); + } + + if (m_videoSettings.frameRate() <= 0) + m_videoSettings.setFrameRate(m_defaultSettings.videoFrameRate); + if (m_videoSettings.bitRate() <= 0) + m_videoSettings.setBitRate(m_defaultSettings.videoBitRate); + + if (m_videoSettings.codec().isEmpty()) + m_videoEncoder = m_defaultSettings.videoEncoder; + else if (m_videoSettings.codec() == QLatin1String("h263")) + m_videoEncoder = AndroidMediaRecorder::H263; + else if (m_videoSettings.codec() == QLatin1String("h264")) + m_videoEncoder = AndroidMediaRecorder::H264; + else if (m_videoSettings.codec() == QLatin1String("mpeg4_sp")) + m_videoEncoder = AndroidMediaRecorder::MPEG_4_SP; + else + m_videoEncoder = m_defaultSettings.videoEncoder; + + m_videoSettingsDirty = false; + } +} + +void QAndroidCaptureSession::updateViewfinder() +{ + m_cameraSession->camera()->stopPreviewSynchronous(); + m_cameraSession->applyViewfinderSettings(m_videoSettings.resolution(), false); +} + +void QAndroidCaptureSession::restartViewfinder() +{ + if (!m_cameraSession) + return; + + m_cameraSession->camera()->reconnect(); + + // This is not necessary on most devices, but it crashes on some if we don't stop the + // preview and reset the preview display on the camera when recording is over. + m_cameraSession->camera()->stopPreviewSynchronous(); + m_cameraSession->videoOutput()->reset(); + if (m_cameraSession->videoOutput()->surfaceTexture()) + m_cameraSession->camera()->setPreviewTexture(m_cameraSession->videoOutput()->surfaceTexture()); + else if (m_cameraSession->videoOutput()->surfaceHolder()) + m_cameraSession->camera()->setPreviewDisplay(m_cameraSession->videoOutput()->surfaceHolder()); + + m_cameraSession->camera()->startPreview(); + m_cameraSession->setReadyForCapture(true); +} + +void QAndroidCaptureSession::updateDuration() +{ + if (m_elapsedTime.isValid()) + m_duration = m_elapsedTime.elapsed(); + + emit durationChanged(m_duration); +} + +void QAndroidCaptureSession::onCameraOpened() +{ + m_supportedResolutions.clear(); + m_supportedFramerates.clear(); + + // get supported resolutions from predefined profiles + for (int i = 0; i < 8; ++i) { + CaptureProfile profile = getProfile(i); + if (!profile.isNull) { + if (i == AndroidCamcorderProfile::QUALITY_HIGH) + m_defaultSettings = profile; + + if (!m_supportedResolutions.contains(profile.videoResolution)) + m_supportedResolutions.append(profile.videoResolution); + if (!m_supportedFramerates.contains(profile.videoFrameRate)) + m_supportedFramerates.append(profile.videoFrameRate); + } + } + + std::sort(m_supportedResolutions.begin(), m_supportedResolutions.end(), qt_sizeLessThan); + std::sort(m_supportedFramerates.begin(), m_supportedFramerates.end()); + + applySettings(); +} + +QAndroidCaptureSession::CaptureProfile QAndroidCaptureSession::getProfile(int id) +{ + CaptureProfile profile; + const bool hasProfile = AndroidCamcorderProfile::hasProfile(m_cameraSession->camera()->cameraId(), + AndroidCamcorderProfile::Quality(id)); + + if (hasProfile) { + AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(m_cameraSession->camera()->cameraId(), + AndroidCamcorderProfile::Quality(id)); + + profile.outputFormat = AndroidMediaRecorder::OutputFormat(camProfile.getValue(AndroidCamcorderProfile::fileFormat)); + profile.audioEncoder = AndroidMediaRecorder::AudioEncoder(camProfile.getValue(AndroidCamcorderProfile::audioCodec)); + profile.audioBitRate = camProfile.getValue(AndroidCamcorderProfile::audioBitRate); + profile.audioChannels = camProfile.getValue(AndroidCamcorderProfile::audioChannels); + profile.audioSampleRate = camProfile.getValue(AndroidCamcorderProfile::audioSampleRate); + profile.videoEncoder = AndroidMediaRecorder::VideoEncoder(camProfile.getValue(AndroidCamcorderProfile::videoCodec)); + profile.videoBitRate = camProfile.getValue(AndroidCamcorderProfile::videoBitRate); + profile.videoFrameRate = camProfile.getValue(AndroidCamcorderProfile::videoFrameRate); + profile.videoResolution = QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth), + camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight)); + + if (profile.outputFormat == AndroidMediaRecorder::MPEG_4) + profile.outputFileExtension = QStringLiteral("mp4"); + else if (profile.outputFormat == AndroidMediaRecorder::THREE_GPP) + profile.outputFileExtension = QStringLiteral("3gp"); + else if (profile.outputFormat == AndroidMediaRecorder::AMR_NB_Format) + profile.outputFileExtension = QStringLiteral("amr"); + else if (profile.outputFormat == AndroidMediaRecorder::AMR_WB_Format) + profile.outputFileExtension = QStringLiteral("awb"); + + profile.isNull = false; + } + + return profile; +} + +void QAndroidCaptureSession::onError(int what, int extra) +{ + Q_UNUSED(what); + Q_UNUSED(extra); + stop(true); + emit error(QMediaRecorder::ResourceError, QLatin1String("Unknown error.")); +} + +void QAndroidCaptureSession::onInfo(int what, int extra) +{ + Q_UNUSED(extra); + if (what == 800) { + // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED + setState(QMediaRecorder::StoppedState); + emit error(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached.")); + } else if (what == 801) { + // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED + setState(QMediaRecorder::StoppedState); + emit error(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached.")); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h new file mode 100644 index 000000000..7ea469334 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidcapturesession_p.h @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** 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 QANDROIDCAPTURESESSION_H +#define QANDROIDCAPTURESESSION_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 <qmediarecorder.h> +#include <qurl.h> +#include <qelapsedtimer.h> +#include <qtimer.h> +#include <private/qmediastoragelocation_p.h> +#include "androidmediarecorder_p.h" + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidCaptureSession : public QObject +{ + Q_OBJECT +public: + explicit QAndroidCaptureSession(QAndroidCameraSession *cameraSession = 0); + ~QAndroidCaptureSession(); + + QList<QSize> supportedResolutions() const { return m_supportedResolutions; } + QList<qreal> supportedFrameRates() const { return m_supportedFramerates; } + + QString audioInput() const { return m_audioInput; } + void setAudioInput(const QString &input); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl &location); + + QMediaRecorder::State state() const; + void setState(QMediaRecorder::State state); + + QMediaRecorder::Status status() const; + + qint64 duration() const; + + QString containerFormat() const { return m_containerFormat; } + void setContainerFormat(const QString &format); + + QAudioEncoderSettings audioSettings() const { return m_audioSettings; } + void setAudioSettings(const QAudioEncoderSettings &settings); + + QVideoEncoderSettings videoSettings() const { return m_videoSettings; } + void setVideoSettings(const QVideoEncoderSettings &settings); + + void applySettings(); + +Q_SIGNALS: + void audioInputChanged(const QString& name); + void stateChanged(QMediaRecorder::State state); + void statusChanged(QMediaRecorder::Status status); + void durationChanged(qint64 position); + void actualLocationChanged(const QUrl &location); + void error(int error, const QString &errorString); + +private Q_SLOTS: + void updateDuration(); + void onCameraOpened(); + + void onError(int what, int extra); + void onInfo(int what, int extra); + +private: + struct CaptureProfile { + AndroidMediaRecorder::OutputFormat outputFormat; + QString outputFileExtension; + + AndroidMediaRecorder::AudioEncoder audioEncoder; + int audioBitRate; + int audioChannels; + int audioSampleRate; + + AndroidMediaRecorder::VideoEncoder videoEncoder; + int videoBitRate; + int videoFrameRate; + QSize videoResolution; + + bool isNull; + + CaptureProfile() + : outputFormat(AndroidMediaRecorder::MPEG_4) + , outputFileExtension(QLatin1String("mp4")) + , audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder) + , audioBitRate(128000) + , audioChannels(2) + , audioSampleRate(44100) + , videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder) + , videoBitRate(1) + , videoFrameRate(-1) + , videoResolution(320, 240) + , isNull(true) + { } + }; + + CaptureProfile getProfile(int id); + + void start(); + void stop(bool error = false); + + void setStatus(QMediaRecorder::Status status); + + void updateViewfinder(); + void restartViewfinder(); + + AndroidMediaRecorder *m_mediaRecorder; + QAndroidCameraSession *m_cameraSession; + + QString m_audioInput; + AndroidMediaRecorder::AudioSource m_audioSource; + + QMediaStorageLocation m_mediaStorageLocation; + + QElapsedTimer m_elapsedTime; + QTimer m_notifyTimer; + qint64 m_duration; + + QMediaRecorder::State m_state; + QMediaRecorder::Status m_status; + QUrl m_requestedOutputLocation; + QUrl m_usedOutputLocation; + QUrl m_actualOutputLocation; + + CaptureProfile m_defaultSettings; + + QString m_containerFormat; + QAudioEncoderSettings m_audioSettings; + QVideoEncoderSettings m_videoSettings; + bool m_containerFormatDirty; + bool m_videoSettingsDirty; + bool m_audioSettingsDirty; + AndroidMediaRecorder::OutputFormat m_outputFormat; + AndroidMediaRecorder::AudioEncoder m_audioEncoder; + AndroidMediaRecorder::VideoEncoder m_videoEncoder; + + QList<QSize> m_supportedResolutions; + QList<qreal> m_supportedFramerates; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDCAPTURESESSION_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol.cpp new file mode 100644 index 000000000..1f30ea95f --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** 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 "qandroidimageencodercontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidImageEncoderControl::QAndroidImageEncoderControl(QAndroidCameraSession *session) + : QImageEncoderControl() + , m_session(session) +{ + connect(m_session, SIGNAL(opened()), + this, SLOT(onCameraOpened())); +} + +QStringList QAndroidImageEncoderControl::supportedImageCodecs() const +{ + return QStringList() << QLatin1String("jpeg"); +} + +QString QAndroidImageEncoderControl::imageCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("jpeg")) + return tr("JPEG image"); + + return QString(); +} + +QList<QSize> QAndroidImageEncoderControl::supportedResolutions(const QImageEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (continuous) + *continuous = false; + + return m_supportedResolutions; +} + +QImageEncoderSettings QAndroidImageEncoderControl::imageSettings() const +{ + return m_session->imageSettings(); +} + +void QAndroidImageEncoderControl::setImageSettings(const QImageEncoderSettings &settings) +{ + m_session->setImageSettings(settings); +} + +void QAndroidImageEncoderControl::onCameraOpened() +{ + m_supportedResolutions = m_session->camera()->getSupportedPictureSizes(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol_p.h new file mode 100644 index 000000000..9e65660ff --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidimageencodercontrol_p.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** 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 QANDROIDIMAGEENCODERCONTROL_H +#define QANDROIDIMAGEENCODERCONTROL_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 <qimageencodercontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidImageEncoderControl : public QImageEncoderControl +{ + Q_OBJECT +public: + explicit QAndroidImageEncoderControl(QAndroidCameraSession *session); + + QStringList supportedImageCodecs() const override; + QString imageCodecDescription(const QString &codecName) const override; + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings, bool *continuous = 0) const override; + QImageEncoderSettings imageSettings() const override; + void setImageSettings(const QImageEncoderSettings &settings) override; + +private Q_SLOTS: + void onCameraOpened(); + +private: + QAndroidCameraSession *m_session; + + QList<QSize> m_supportedResolutions; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDIMAGEENCODERCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol.cpp new file mode 100644 index 000000000..45fe7092c --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 "qandroidmediacontainercontrol_p.h" + +#include "qandroidcapturesession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidMediaContainerControl::QAndroidMediaContainerControl(QAndroidCaptureSession *session) + : QMediaContainerControl() + , m_session(session) +{ +} + +QStringList QAndroidMediaContainerControl::supportedContainers() const +{ + return QStringList() << QLatin1String("mp4") + << QLatin1String("3gp") + << QLatin1String("amr") + << QLatin1String("awb"); +} + +QString QAndroidMediaContainerControl::containerFormat() const +{ + return m_session->containerFormat(); +} + +void QAndroidMediaContainerControl::setContainerFormat(const QString &format) +{ + m_session->setContainerFormat(format); +} + +QString QAndroidMediaContainerControl::containerDescription(const QString &formatMimeType) const +{ + if (formatMimeType == QLatin1String("mp4")) + return tr("MPEG4 media file format"); + else if (formatMimeType == QLatin1String("3gp")) + return tr("3GPP media file format"); + else if (formatMimeType == QLatin1String("amr")) + return tr("AMR NB file format"); + else if (formatMimeType == QLatin1String("awb")) + return tr("AMR WB file format"); + + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol_p.h new file mode 100644 index 000000000..00f09970f --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediacontainercontrol_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIACONTAINERCONTROL_H +#define QANDROIDMEDIACONTAINERCONTROL_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 <qmediacontainercontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCaptureSession; + +class QAndroidMediaContainerControl : public QMediaContainerControl +{ + Q_OBJECT +public: + QAndroidMediaContainerControl(QAndroidCaptureSession *session); + + QStringList supportedContainers() const override; + QString containerFormat() const override; + void setContainerFormat(const QString &format) override; + QString containerDescription(const QString &formatMimeType) const override; + +private: + QAndroidCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIACONTAINERCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol.cpp new file mode 100644 index 000000000..7a1913afc --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 "qandroidmediarecordercontrol_p.h" + +#include "qandroidcapturesession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidMediaRecorderControl::QAndroidMediaRecorderControl(QAndroidCaptureSession *session) + : QMediaRecorderControl() + , m_session(session) +{ + connect(m_session, SIGNAL(stateChanged(QMediaRecorder::State)), this, SIGNAL(stateChanged(QMediaRecorder::State))); + connect(m_session, SIGNAL(statusChanged(QMediaRecorder::Status)), this, SIGNAL(statusChanged(QMediaRecorder::Status))); + connect(m_session, SIGNAL(durationChanged(qint64)), this, SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(actualLocationChanged(QUrl)), this, SIGNAL(actualLocationChanged(QUrl))); + connect(m_session, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString))); +} + +QUrl QAndroidMediaRecorderControl::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool QAndroidMediaRecorderControl::setOutputLocation(const QUrl &location) +{ + return m_session->setOutputLocation(location); +} + +QMediaRecorder::State QAndroidMediaRecorderControl::state() const +{ + return m_session->state(); +} + +QMediaRecorder::Status QAndroidMediaRecorderControl::status() const +{ + return m_session->status(); +} + +qint64 QAndroidMediaRecorderControl::duration() const +{ + return m_session->duration(); +} + +bool QAndroidMediaRecorderControl::isMuted() const +{ + // No API for this in Android + return false; +} + +qreal QAndroidMediaRecorderControl::volume() const +{ + // No API for this in Android + return 1.0; +} + +void QAndroidMediaRecorderControl::applySettings() +{ + m_session->applySettings(); +} + +void QAndroidMediaRecorderControl::setState(QMediaRecorder::State state) +{ + m_session->setState(state); +} + +void QAndroidMediaRecorderControl::setMuted(bool muted) +{ + // No API for this in Android + Q_UNUSED(muted); + qWarning("QMediaRecorder::setMuted() is not supported on Android."); +} + +void QAndroidMediaRecorderControl::setVolume(qreal volume) +{ + // No API for this in Android + Q_UNUSED(volume); + qWarning("QMediaRecorder::setVolume() is not supported on Android."); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol_p.h new file mode 100644 index 000000000..4f6685d28 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediarecordercontrol_p.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIARECORDERCONTROL_H +#define QANDROIDMEDIARECORDERCONTROL_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 <qmediarecordercontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCaptureSession; + +class QAndroidMediaRecorderControl : public QMediaRecorderControl +{ + Q_OBJECT +public: + explicit QAndroidMediaRecorderControl(QAndroidCaptureSession *session); + + QUrl outputLocation() const override; + bool setOutputLocation(const QUrl &location) override; + QMediaRecorder::State state() const override; + QMediaRecorder::Status status() const override; + qint64 duration() const override; + bool isMuted() const override; + qreal volume() const override; + void applySettings() override; + +public Q_SLOTS: + void setState(QMediaRecorder::State state) override; + void setMuted(bool muted) override; + void setVolume(qreal volume) override; + +private: + QAndroidCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIARECORDERCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol.cpp new file mode 100644 index 000000000..86321271a --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol.cpp @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Integrated Computer Solutions, Inc +** 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 "qandroidmediavideoprobecontrol_p.h" +#include <qvideoframe.h> + +QT_BEGIN_NAMESPACE + +QAndroidMediaVideoProbeControl::QAndroidMediaVideoProbeControl(QObject *parent) : + QMediaVideoProbeControl(parent) +{ +} + +QAndroidMediaVideoProbeControl::~QAndroidMediaVideoProbeControl() +{ + +} + +void QAndroidMediaVideoProbeControl::newFrameProbed(const QVideoFrame &frame) +{ + emit videoFrameProbed(frame); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol_p.h new file mode 100644 index 000000000..324370e97 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidmediavideoprobecontrol_p.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Integrated Computer Solutions, Inc +** 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 QANDROIDMEDIAVIDEOPROBECONTROL_H +#define QANDROIDMEDIAVIDEOPROBECONTROL_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 <qmediavideoprobecontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidMediaVideoProbeControl : public QMediaVideoProbeControl +{ + Q_OBJECT +public: + explicit QAndroidMediaVideoProbeControl(QObject *parent = 0); + virtual ~QAndroidMediaVideoProbeControl(); + + void newFrameProbed(const QVideoFrame& frame); + +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIAVIDEOPROBECONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol.cpp new file mode 100644 index 000000000..5403647f7 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol.cpp @@ -0,0 +1,115 @@ +/**************************************************************************** +** +** 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 "qandroidvideodeviceselectorcontrol_p.h" + +#include "qandroidcamerasession_p.h" +#include "androidcamera_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidVideoDeviceSelectorControl::QAndroidVideoDeviceSelectorControl(QAndroidCameraSession *session) + : QVideoDeviceSelectorControl(0) + , m_selectedDevice(0) + , m_cameraSession(session) +{ +} + +QAndroidVideoDeviceSelectorControl::~QAndroidVideoDeviceSelectorControl() +{ +} + +int QAndroidVideoDeviceSelectorControl::deviceCount() const +{ + return QAndroidCameraSession::availableCameras().count(); +} + +QString QAndroidVideoDeviceSelectorControl::deviceName(int index) const +{ + if (index < 0 || index >= QAndroidCameraSession::availableCameras().count()) + return QString(); + + return QString::fromLatin1(QAndroidCameraSession::availableCameras().at(index).name); +} + +QString QAndroidVideoDeviceSelectorControl::deviceDescription(int index) const +{ + if (index < 0 || index >= QAndroidCameraSession::availableCameras().count()) + return QString(); + + return QAndroidCameraSession::availableCameras().at(index).description; +} + +QCamera::Position QAndroidVideoDeviceSelectorControl::cameraPosition(int index) const +{ + if (index < 0 || index >= QAndroidCameraSession::availableCameras().count()) + return QCamera::UnspecifiedPosition; + + return QAndroidCameraSession::availableCameras().at(index).position; +} + +int QAndroidVideoDeviceSelectorControl::cameraOrientation(int index) const +{ + if (index < 0 || index >= QAndroidCameraSession::availableCameras().count()) + return QCamera::UnspecifiedPosition; + + return QAndroidCameraSession::availableCameras().at(index).orientation; +} + +int QAndroidVideoDeviceSelectorControl::defaultDevice() const +{ + return 0; +} + +int QAndroidVideoDeviceSelectorControl::selectedDevice() const +{ + return m_selectedDevice; +} + +void QAndroidVideoDeviceSelectorControl::setSelectedDevice(int index) +{ + if (index != m_selectedDevice) { + m_selectedDevice = index; + m_cameraSession->setSelectedCamera(m_selectedDevice); + emit selectedDeviceChanged(index); + emit selectedDeviceChanged(deviceName(index)); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol_p.h new file mode 100644 index 000000000..d074e7441 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidvideodeviceselectorcontrol_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** 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 QANDROIDVIDEODEVICESELECTORCONTROL_H +#define QANDROIDVIDEODEVICESELECTORCONTROL_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 <qvideodeviceselectorcontrol.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCameraSession; + +class QAndroidVideoDeviceSelectorControl : public QVideoDeviceSelectorControl +{ + Q_OBJECT +public: + explicit QAndroidVideoDeviceSelectorControl(QAndroidCameraSession *session); + ~QAndroidVideoDeviceSelectorControl(); + + int deviceCount() const; + + QString deviceName(int index) const; + QString deviceDescription(int index) const; + QCamera::Position cameraPosition(int index) const; + int cameraOrientation(int index) const; + + int defaultDevice() const; + int selectedDevice() const; + void setSelectedDevice(int index); + +private: + int m_selectedDevice; + + QAndroidCameraSession *m_cameraSession; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDVIDEODEVICESELECTORCONTROL_H diff --git a/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol.cpp b/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol.cpp new file mode 100644 index 000000000..ef3559c0d --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** 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 "qandroidvideoencodersettingscontrol_p.h" + +#include "qandroidcapturesession_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidVideoEncoderSettingsControl::QAndroidVideoEncoderSettingsControl(QAndroidCaptureSession *session) + : QVideoEncoderSettingsControl() + , m_session(session) +{ +} + +QList<QSize> QAndroidVideoEncoderSettingsControl::supportedResolutions(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = false; + + return m_session->supportedResolutions(); +} + +QList<qreal> QAndroidVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = false; + + return m_session->supportedFrameRates(); +} + +QStringList QAndroidVideoEncoderSettingsControl::supportedVideoCodecs() const +{ + return QStringList() << QLatin1String("h263") + << QLatin1String("h264") + << QLatin1String("mpeg4_sp"); +} + +QString QAndroidVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("h263")) + return tr("H.263 compression"); + else if (codecName == QLatin1String("h264")) + return tr("H.264 compression"); + else if (codecName == QLatin1String("mpeg4_sp")) + return tr("MPEG-4 SP compression"); + + return QString(); +} + +QVideoEncoderSettings QAndroidVideoEncoderSettingsControl::videoSettings() const +{ + return m_session->videoSettings(); +} + +void QAndroidVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_session->setVideoSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol_p.h b/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol_p.h new file mode 100644 index 000000000..1488790a8 --- /dev/null +++ b/src/multimedia/platform/android/mediacapture/qandroidvideoencodersettingscontrol_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 QANDROIDVIDEOENCODERSETTINGSCONTROL_H +#define QANDROIDVIDEOENCODERSETTINGSCONTROL_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 <qvideoencodersettingscontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidCaptureSession; + +class QAndroidVideoEncoderSettingsControl : public QVideoEncoderSettingsControl +{ + Q_OBJECT +public: + explicit QAndroidVideoEncoderSettingsControl(QAndroidCaptureSession *session); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous = 0) const override; + QList<qreal> supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous = 0) const override; + QStringList supportedVideoCodecs() const override; + QString videoCodecDescription(const QString &codecName) const override; + QVideoEncoderSettings videoSettings() const override; + void setVideoSettings(const QVideoEncoderSettings &settings) override; + +private: + QAndroidCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDVIDEOENCODERSETTINGSCONTROL_H diff --git a/src/multimedia/platform/android/mediaplayer/mediaplayer.pri b/src/multimedia/platform/android/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..33348b38b --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/mediaplayer.pri @@ -0,0 +1,13 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qandroidmediaplayercontrol_p.h \ + $$PWD/qandroidmediaservice_p.h \ + $$PWD/qandroidmetadatareadercontrol_p.h \ + $$PWD/qandroidmediaplayervideorenderercontrol_p.h + +SOURCES += \ + $$PWD/qandroidmediaplayercontrol.cpp \ + $$PWD/qandroidmediaservice.cpp \ + $$PWD/qandroidmetadatareadercontrol.cpp \ + $$PWD/qandroidmediaplayervideorenderercontrol.cpp diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol.cpp new file mode 100644 index 000000000..7e4f32e4a --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol.cpp @@ -0,0 +1,835 @@ +/**************************************************************************** +** +** 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 "qandroidmediaplayercontrol_p.h" +#include "androidmediaplayer_p.h" +#include "qandroidvideooutput_p.h" + +QT_BEGIN_NAMESPACE + +class StateChangeNotifier +{ +public: + StateChangeNotifier(QAndroidMediaPlayerControl *mp) + : mControl(mp) + , mPreviousState(mp->state()) + , mPreviousMediaStatus(mp->mediaStatus()) + { + ++mControl->mActiveStateChangeNotifiers; + } + + ~StateChangeNotifier() + { + if (--mControl->mActiveStateChangeNotifiers) + return; + + if (mPreviousMediaStatus != mControl->mediaStatus()) + Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus()); + + if (mPreviousState != mControl->state()) + Q_EMIT mControl->stateChanged(mControl->state()); + } + +private: + QAndroidMediaPlayerControl *mControl; + QMediaPlayer::State mPreviousState; + QMediaPlayer::MediaStatus mPreviousMediaStatus; +}; + + +QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent) + : QMediaPlayerControl(parent), + mMediaPlayer(new AndroidMediaPlayer), + mCurrentState(QMediaPlayer::StoppedState), + mCurrentMediaStatus(QMediaPlayer::NoMedia), + mMediaStream(0), + mVideoOutput(0), + mSeekable(true), + mBufferPercent(-1), + mBufferFilled(false), + mAudioAvailable(false), + mVideoAvailable(false), + mBuffering(false), + mState(AndroidMediaPlayer::Uninitialized), + mPendingState(-1), + mPendingPosition(-1), + mPendingSetMedia(false), + mPendingVolume(-1), + mPendingMute(-1), + mReloadingMedia(false), + mActiveStateChangeNotifiers(0), + mPendingPlaybackRate(1.0), + mHasPendingPlaybackRate(false) +{ + connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)), + this,SLOT(onBufferingChanged(qint32))); + connect(mMediaPlayer,SIGNAL(info(qint32,qint32)), + this,SLOT(onInfo(qint32,qint32))); + connect(mMediaPlayer,SIGNAL(error(qint32,qint32)), + this,SLOT(onError(qint32,qint32))); + connect(mMediaPlayer,SIGNAL(stateChanged(qint32)), + this,SLOT(onStateChanged(qint32))); + connect(mMediaPlayer,SIGNAL(videoSizeChanged(qint32,qint32)), + this,SLOT(onVideoSizeChanged(qint32,qint32))); + connect(mMediaPlayer,SIGNAL(progressChanged(qint64)), + this,SIGNAL(positionChanged(qint64))); + connect(mMediaPlayer,SIGNAL(durationChanged(qint64)), + this,SIGNAL(durationChanged(qint64))); +} + +QAndroidMediaPlayerControl::~QAndroidMediaPlayerControl() +{ + mMediaPlayer->release(); + delete mMediaPlayer; +} + +QMediaPlayer::State QAndroidMediaPlayerControl::state() const +{ + return mCurrentState; +} + +QMediaPlayer::MediaStatus QAndroidMediaPlayerControl::mediaStatus() const +{ + return mCurrentMediaStatus; +} + +qint64 QAndroidMediaPlayerControl::duration() const +{ + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::Stopped + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + return 0; + } + + return mMediaPlayer->getDuration(); +} + +qint64 QAndroidMediaPlayerControl::position() const +{ + if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia) + return duration(); + + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted))) { + return mMediaPlayer->getCurrentPosition(); + } + + return (mPendingPosition == -1) ? 0 : mPendingPosition; +} + +void QAndroidMediaPlayerControl::setPosition(qint64 position) +{ + if (!mSeekable) + return; + + const int seekPosition = (position > INT_MAX) ? INT_MAX : position; + + if (seekPosition == this->position()) + return; + + StateChangeNotifier notifier(this); + + if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia) + setMediaStatus(QMediaPlayer::LoadedMedia); + + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + mPendingPosition = seekPosition; + } else { + mMediaPlayer->seekTo(seekPosition); + + if (mPendingPosition != -1) { + mPendingPosition = -1; + } + } + + Q_EMIT positionChanged(seekPosition); +} + +int QAndroidMediaPlayerControl::volume() const +{ + return (mPendingVolume == -1) ? mMediaPlayer->volume() : mPendingVolume; +} + +void QAndroidMediaPlayerControl::setVolume(int volume) +{ + if ((mState & (AndroidMediaPlayer::Idle + | AndroidMediaPlayer::Initialized + | AndroidMediaPlayer::Stopped + | AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + if (mPendingVolume != volume) { + mPendingVolume = volume; + Q_EMIT volumeChanged(volume); + } + return; + } + + mMediaPlayer->setVolume(volume); + + if (mPendingVolume != -1) { + mPendingVolume = -1; + return; + } + + Q_EMIT volumeChanged(volume); +} + +bool QAndroidMediaPlayerControl::isMuted() const +{ + return (mPendingMute == -1) ? mMediaPlayer->isMuted() : (mPendingMute == 1); +} + +void QAndroidMediaPlayerControl::setMuted(bool muted) +{ + if ((mState & (AndroidMediaPlayer::Idle + | AndroidMediaPlayer::Initialized + | AndroidMediaPlayer::Stopped + | AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + if (mPendingMute != muted) { + mPendingMute = muted; + Q_EMIT mutedChanged(muted); + } + return; + } + + mMediaPlayer->setMuted(muted); + + if (mPendingMute != -1) { + mPendingMute = -1; + return; + } + + Q_EMIT mutedChanged(muted); +} + +void QAndroidMediaPlayerControl::setAudioRole(QAudio::Role role) +{ + mMediaPlayer->setAudioRole(role); +} + +QList<QAudio::Role> QAndroidMediaPlayerControl::supportedAudioRoles() const +{ + return QList<QAudio::Role>() + << QAudio::VoiceCommunicationRole + << QAudio::MusicRole + << QAudio::VideoRole + << QAudio::SonificationRole + << QAudio::AlarmRole + << QAudio::NotificationRole + << QAudio::RingtoneRole + << QAudio::AccessibilityRole + << QAudio::GameRole; +} + +void QAndroidMediaPlayerControl::setCustomAudioRole(const QString &role) +{ + mMediaPlayer->setCustomAudioRole(role); +} + +QStringList QAndroidMediaPlayerControl::supportedCustomAudioRoles() const +{ + return QStringList() + << "CONTENT_TYPE_MOVIE" + << "CONTENT_TYPE_MUSIC" + << "CONTENT_TYPE_SONIFICATION" + << "CONTENT_TYPE_SPEECH" + << "USAGE_ALARM" + << "USAGE_ASSISTANCE_ACCESSIBILITY" + << "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE" + << "USAGE_ASSISTANCE_SONIFICATION" + << "USAGE_ASSISTANT" + << "USAGE_GAME" + << "USAGE_MEDIA" + << "USAGE_NOTIFICATION" + << "USAGE_NOTIFICATION_COMMUNICATION_DELAYED" + << "USAGE_NOTIFICATION_COMMUNICATION_INSTANT" + << "USAGE_NOTIFICATION_COMMUNICATION_REQUEST" + << "USAGE_NOTIFICATION_EVENT" + << "USAGE_NOTIFICATION_RINGTONE" + << "USAGE_VOICE_COMMUNICATION" + << "USAGE_VOICE_COMMUNICATION_SIGNALLING"; +} + +int QAndroidMediaPlayerControl::bufferStatus() const +{ + return mBufferFilled ? 100 : 0; +} + +bool QAndroidMediaPlayerControl::isAudioAvailable() const +{ + return mAudioAvailable; +} + +bool QAndroidMediaPlayerControl::isVideoAvailable() const +{ + return mVideoAvailable; +} + +bool QAndroidMediaPlayerControl::isSeekable() const +{ + return mSeekable; +} + +QMediaTimeRange QAndroidMediaPlayerControl::availablePlaybackRanges() const +{ + return mAvailablePlaybackRange; +} + +void QAndroidMediaPlayerControl::updateAvailablePlaybackRanges() +{ + if (mBuffering) { + const qint64 pos = position(); + const qint64 end = (duration() / 100) * mBufferPercent; + mAvailablePlaybackRange.addInterval(pos, end); + } else if (mSeekable) { + mAvailablePlaybackRange = QMediaTimeRange(0, duration()); + } else { + mAvailablePlaybackRange = QMediaTimeRange(); + } + + Q_EMIT availablePlaybackRangesChanged(mAvailablePlaybackRange); +} + +qreal QAndroidMediaPlayerControl::playbackRate() const +{ + if (mHasPendingPlaybackRate || + (mState & (AndroidMediaPlayer::Initialized + | AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted + | AndroidMediaPlayer::Error)) == 0) { + return mPendingPlaybackRate; + } + + return mMediaPlayer->playbackRate(); +} + +void QAndroidMediaPlayerControl::setPlaybackRate(qreal rate) +{ + if ((mState & (AndroidMediaPlayer::Initialized + | AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted + | AndroidMediaPlayer::Error)) == 0) { + if (mPendingPlaybackRate != rate) { + mPendingPlaybackRate = rate; + mHasPendingPlaybackRate = true; + Q_EMIT playbackRateChanged(rate); + } + return; + } + + bool succeeded = mMediaPlayer->setPlaybackRate(rate); + + if (mHasPendingPlaybackRate) { + mHasPendingPlaybackRate = false; + mPendingPlaybackRate = qreal(1.0); + if (!succeeded) + Q_EMIT playbackRateChanged(playbackRate()); + } else if (succeeded) { + Q_EMIT playbackRateChanged(rate); + } +} + +QUrl QAndroidMediaPlayerControl::media() const +{ + return mMediaContent; +} + +const QIODevice *QAndroidMediaPlayerControl::mediaStream() const +{ + return mMediaStream; +} + +void QAndroidMediaPlayerControl::setMedia(const QUrl &mediaContent, + QIODevice *stream) +{ + StateChangeNotifier notifier(this); + + mReloadingMedia = (mMediaContent == mediaContent) && !mPendingSetMedia; + + if (!mReloadingMedia) { + mMediaContent = mediaContent; + mMediaStream = stream; + } + + // Release the mediaplayer if it's not in in Idle or Uninitialized state + if ((mState & (AndroidMediaPlayer::Idle | AndroidMediaPlayer::Uninitialized)) == 0) + mMediaPlayer->release(); + + if (mediaContent.isNull()) { + setMediaStatus(QMediaPlayer::NoMedia); + } else { + if (mVideoOutput && !mVideoOutput->isReady()) { + // if a video output is set but the video texture is not ready, delay loading the media + // since it can cause problems on some hardware + mPendingSetMedia = true; + return; + } + + if (mVideoSize.isValid() && mVideoOutput) + mVideoOutput->setVideoSize(mVideoSize); + + if ((mMediaPlayer->display() == 0) && mVideoOutput) + mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); + mMediaPlayer->setDataSource(mediaContent.request()); + mMediaPlayer->prepareAsync(); + } + + if (!mReloadingMedia) + Q_EMIT mediaChanged(mMediaContent); + + resetBufferingProgress(); + + mReloadingMedia = false; +} + +void QAndroidMediaPlayerControl::setVideoOutput(QAndroidVideoOutput *videoOutput) +{ + if (mVideoOutput) { + mMediaPlayer->setDisplay(0); + mVideoOutput->stop(); + mVideoOutput->reset(); + } + + mVideoOutput = videoOutput; + + if (!mVideoOutput) + return; + + if (mVideoOutput->isReady()) + mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); + + connect(videoOutput, SIGNAL(readyChanged(bool)), this, SLOT(onVideoOutputReady(bool))); +} + +void QAndroidMediaPlayerControl::play() +{ + StateChangeNotifier notifier(this); + + // We need to prepare the mediaplayer again. + if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isNull()) { + setMedia(mMediaContent, mMediaStream); + } + + if (!mMediaContent.isNull()) + setState(QMediaPlayer::PlayingState); + + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + mPendingState = QMediaPlayer::PlayingState; + return; + } + + mMediaPlayer->play(); +} + +void QAndroidMediaPlayerControl::pause() +{ + StateChangeNotifier notifier(this); + + setState(QMediaPlayer::PausedState); + + if ((mState & (AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + mPendingState = QMediaPlayer::PausedState; + return; + } + + mMediaPlayer->pause(); +} + +void QAndroidMediaPlayerControl::stop() +{ + StateChangeNotifier notifier(this); + + setState(QMediaPlayer::StoppedState); + + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Stopped + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + if ((mState & (AndroidMediaPlayer::Idle | AndroidMediaPlayer::Uninitialized | AndroidMediaPlayer::Error)) == 0) + mPendingState = QMediaPlayer::StoppedState; + return; + } + + mMediaPlayer->stop(); +} + +void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra) +{ + StateChangeNotifier notifier(this); + + Q_UNUSED(extra); + switch (what) { + case AndroidMediaPlayer::MEDIA_INFO_UNKNOWN: + break; + case AndroidMediaPlayer::MEDIA_INFO_VIDEO_TRACK_LAGGING: + // IGNORE + break; + case AndroidMediaPlayer::MEDIA_INFO_VIDEO_RENDERING_START: + break; + case AndroidMediaPlayer::MEDIA_INFO_BUFFERING_START: + mPendingState = mCurrentState; + setState(QMediaPlayer::PausedState); + setMediaStatus(QMediaPlayer::StalledMedia); + break; + case AndroidMediaPlayer::MEDIA_INFO_BUFFERING_END: + if (mCurrentState != QMediaPlayer::StoppedState) + flushPendingStates(); + break; + case AndroidMediaPlayer::MEDIA_INFO_BAD_INTERLEAVING: + break; + case AndroidMediaPlayer::MEDIA_INFO_NOT_SEEKABLE: + setSeekable(false); + break; + case AndroidMediaPlayer::MEDIA_INFO_METADATA_UPDATE: + Q_EMIT metaDataUpdated(); + break; + } +} + +void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra) +{ + StateChangeNotifier notifier(this); + + QString errorString; + QMediaPlayer::Error error = QMediaPlayer::ResourceError; + + switch (what) { + case AndroidMediaPlayer::MEDIA_ERROR_UNKNOWN: + errorString = QLatin1String("Error:"); + break; + case AndroidMediaPlayer::MEDIA_ERROR_SERVER_DIED: + errorString = QLatin1String("Error: Server died"); + error = QMediaPlayer::ServiceMissingError; + break; + case AndroidMediaPlayer::MEDIA_ERROR_INVALID_STATE: + errorString = QLatin1String("Error: Invalid state"); + error = QMediaPlayer::ServiceMissingError; + break; + } + + switch (extra) { + case AndroidMediaPlayer::MEDIA_ERROR_IO: // Network OR file error + errorString += QLatin1String(" (I/O operation failed)"); + error = QMediaPlayer::NetworkError; + setMediaStatus(QMediaPlayer::InvalidMedia); + break; + case AndroidMediaPlayer::MEDIA_ERROR_MALFORMED: + errorString += QLatin1String(" (Malformed bitstream)"); + error = QMediaPlayer::FormatError; + setMediaStatus(QMediaPlayer::InvalidMedia); + break; + case AndroidMediaPlayer::MEDIA_ERROR_UNSUPPORTED: + errorString += QLatin1String(" (Unsupported media)"); + error = QMediaPlayer::FormatError; + setMediaStatus(QMediaPlayer::InvalidMedia); + break; + case AndroidMediaPlayer::MEDIA_ERROR_TIMED_OUT: + errorString += QLatin1String(" (Timed out)"); + break; + case AndroidMediaPlayer::MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK: + errorString += QLatin1String(" (Unable to start progressive playback')"); + error = QMediaPlayer::FormatError; + setMediaStatus(QMediaPlayer::InvalidMedia); + break; + case AndroidMediaPlayer::MEDIA_ERROR_BAD_THINGS_ARE_GOING_TO_HAPPEN: + errorString += QLatin1String(" (Unknown error/Insufficient resources)"); + error = QMediaPlayer::ServiceMissingError; + break; + } + + Q_EMIT QMediaPlayerControl::error(error, errorString); +} + +void QAndroidMediaPlayerControl::onBufferingChanged(qint32 percent) +{ + StateChangeNotifier notifier(this); + + mBuffering = percent != 100; + mBufferPercent = percent; + + updateAvailablePlaybackRanges(); + + if (mCurrentState != QMediaPlayer::StoppedState) + setMediaStatus(mBuffering ? QMediaPlayer::BufferingMedia : QMediaPlayer::BufferedMedia); +} + +void QAndroidMediaPlayerControl::onVideoSizeChanged(qint32 width, qint32 height) +{ + QSize newSize(width, height); + + if (width == 0 || height == 0 || newSize == mVideoSize) + return; + + setVideoAvailable(true); + mVideoSize = newSize; + + if (mVideoOutput) + mVideoOutput->setVideoSize(mVideoSize); +} + +void QAndroidMediaPlayerControl::onStateChanged(qint32 state) +{ + // If reloading, don't report state changes unless the new state is Prepared or Error. + if ((mState & AndroidMediaPlayer::Stopped) + && (state & (AndroidMediaPlayer::Prepared | AndroidMediaPlayer::Error | AndroidMediaPlayer::Uninitialized)) == 0) { + return; + } + + StateChangeNotifier notifier(this); + + mState = state; + switch (mState) { + case AndroidMediaPlayer::Idle: + break; + case AndroidMediaPlayer::Initialized: + break; + case AndroidMediaPlayer::Preparing: + if (!mReloadingMedia) + setMediaStatus(QMediaPlayer::LoadingMedia); + break; + case AndroidMediaPlayer::Prepared: + setMediaStatus(QMediaPlayer::LoadedMedia); + if (mBuffering) { + setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia + : QMediaPlayer::BufferingMedia); + } else { + onBufferingChanged(100); + } + Q_EMIT metaDataUpdated(); + setAudioAvailable(true); + flushPendingStates(); + break; + case AndroidMediaPlayer::Started: + setState(QMediaPlayer::PlayingState); + if (mBuffering) { + setMediaStatus(mBufferPercent == 100 ? QMediaPlayer::BufferedMedia + : QMediaPlayer::BufferingMedia); + } else { + setMediaStatus(QMediaPlayer::BufferedMedia); + } + Q_EMIT positionChanged(position()); + break; + case AndroidMediaPlayer::Paused: + setState(QMediaPlayer::PausedState); + break; + case AndroidMediaPlayer::Error: + setState(QMediaPlayer::StoppedState); + setMediaStatus(QMediaPlayer::UnknownMediaStatus); + mMediaPlayer->release(); + Q_EMIT positionChanged(0); + break; + case AndroidMediaPlayer::Stopped: + setState(QMediaPlayer::StoppedState); + setMediaStatus(QMediaPlayer::LoadedMedia); + Q_EMIT positionChanged(0); + break; + case AndroidMediaPlayer::PlaybackCompleted: + setState(QMediaPlayer::StoppedState); + setMediaStatus(QMediaPlayer::EndOfMedia); + break; + case AndroidMediaPlayer::Uninitialized: + // reset some properties (unless we reload the same media) + if (!mReloadingMedia) { + resetBufferingProgress(); + mPendingPosition = -1; + mPendingSetMedia = false; + mPendingState = -1; + + Q_EMIT durationChanged(0); + Q_EMIT positionChanged(0); + + setAudioAvailable(false); + setVideoAvailable(false); + setSeekable(true); + } + break; + default: + break; + } + + if ((mState & (AndroidMediaPlayer::Stopped | AndroidMediaPlayer::Uninitialized)) != 0) { + mMediaPlayer->setDisplay(0); + if (mVideoOutput) { + mVideoOutput->stop(); + mVideoOutput->reset(); + } + } +} + +void QAndroidMediaPlayerControl::onVideoOutputReady(bool ready) +{ + if ((mMediaPlayer->display() == 0) && mVideoOutput && ready) + mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); + + flushPendingStates(); +} + +void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state) +{ + if (mCurrentState == state) + return; + + if (mCurrentState == QMediaPlayer::StoppedState && state == QMediaPlayer::PausedState) + return; + + mCurrentState = state; +} + +void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status) +{ + if (mCurrentMediaStatus == status) + return; + + mCurrentMediaStatus = status; + + if (status == QMediaPlayer::NoMedia || status == QMediaPlayer::InvalidMedia) + Q_EMIT durationChanged(0); + + if (status == QMediaPlayer::EndOfMedia) + Q_EMIT positionChanged(position()); + + updateBufferStatus(); +} + +void QAndroidMediaPlayerControl::setSeekable(bool seekable) +{ + if (mSeekable == seekable) + return; + + mSeekable = seekable; + Q_EMIT seekableChanged(mSeekable); +} + +void QAndroidMediaPlayerControl::setAudioAvailable(bool available) +{ + if (mAudioAvailable == available) + return; + + mAudioAvailable = available; + Q_EMIT audioAvailableChanged(mAudioAvailable); +} + +void QAndroidMediaPlayerControl::setVideoAvailable(bool available) +{ + if (mVideoAvailable == available) + return; + + if (!available) + mVideoSize = QSize(); + + mVideoAvailable = available; + Q_EMIT videoAvailableChanged(mVideoAvailable); +} + +void QAndroidMediaPlayerControl::resetBufferingProgress() +{ + mBuffering = false; + mBufferPercent = 0; + mAvailablePlaybackRange = QMediaTimeRange(); +} + +void QAndroidMediaPlayerControl::flushPendingStates() +{ + if (mPendingSetMedia) { + setMedia(mMediaContent, 0); + mPendingSetMedia = false; + return; + } + + const int newState = mPendingState; + mPendingState = -1; + + if (mPendingPosition != -1) + setPosition(mPendingPosition); + if (mPendingVolume != -1) + setVolume(mPendingVolume); + if (mPendingMute != -1) + setMuted((mPendingMute == 1)); + if (mHasPendingPlaybackRate) + setPlaybackRate(mPendingPlaybackRate); + + switch (newState) { + case QMediaPlayer::PlayingState: + play(); + break; + case QMediaPlayer::PausedState: + pause(); + break; + case QMediaPlayer::StoppedState: + stop(); + break; + default: + break; + } +} + +void QAndroidMediaPlayerControl::updateBufferStatus() +{ + bool bufferFilled = (mCurrentMediaStatus == QMediaPlayer::BufferedMedia + || mCurrentMediaStatus == QMediaPlayer::BufferingMedia); + + if (mBufferFilled != bufferFilled) { + mBufferFilled = bufferFilled; + Q_EMIT bufferStatusChanged(bufferStatus()); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol_p.h new file mode 100644 index 000000000..b7246acb4 --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayercontrol_p.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIAPLAYERCONTROL_H +#define QANDROIDMEDIAPLAYERCONTROL_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 <QMediaPlayerControl> +#include <qsize.h> + +QT_BEGIN_NAMESPACE + +class AndroidMediaPlayer; +class QAndroidVideoOutput; + +class QAndroidMediaPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT +public: + explicit QAndroidMediaPlayerControl(QObject *parent = 0); + ~QAndroidMediaPlayerControl() override; + + QMediaPlayer::State state() const override; + QMediaPlayer::MediaStatus mediaStatus() const override; + qint64 duration() const override; + qint64 position() const override; + int volume() const override; + bool isMuted() const override; + int bufferStatus() const override; + bool isAudioAvailable() const override; + bool isVideoAvailable() const override; + bool isSeekable() const override; + QMediaTimeRange availablePlaybackRanges() const override; + qreal playbackRate() const override; + void setPlaybackRate(qreal rate) override; + QUrl media() const override; + const QIODevice *mediaStream() const override; + void setMedia(const QUrl &mediaContent, QIODevice *stream) override; + + void setAudioRole(QAudio::Role role) override; + QList<QAudio::Role> supportedAudioRoles() const override; + void setCustomAudioRole(const QString &role) override; + QStringList supportedCustomAudioRoles() const override; + + void setVideoOutput(QAndroidVideoOutput *videoOutput); + +Q_SIGNALS: + void metaDataUpdated(); + +public Q_SLOTS: + void setPosition(qint64 position) override; + void play() override; + void pause() override; + void stop() override; + void setVolume(int volume) override; + void setMuted(bool muted) override; + +private Q_SLOTS: + void onVideoOutputReady(bool ready); + void onError(qint32 what, qint32 extra); + void onInfo(qint32 what, qint32 extra); + void onBufferingChanged(qint32 percent); + void onVideoSizeChanged(qint32 width, qint32 height); + void onStateChanged(qint32 state); + +private: + AndroidMediaPlayer *mMediaPlayer; + QMediaPlayer::State mCurrentState; + QMediaPlayer::MediaStatus mCurrentMediaStatus; + QUrl mMediaContent; + QIODevice *mMediaStream; + QAndroidVideoOutput *mVideoOutput; + bool mSeekable; + int mBufferPercent; + bool mBufferFilled; + bool mAudioAvailable; + bool mVideoAvailable; + QSize mVideoSize; + bool mBuffering; + QMediaTimeRange mAvailablePlaybackRange; + int mState; + int mPendingState; + qint64 mPendingPosition; + bool mPendingSetMedia; + int mPendingVolume; + int mPendingMute; + bool mReloadingMedia; + int mActiveStateChangeNotifiers; + qreal mPendingPlaybackRate; + bool mHasPendingPlaybackRate; // we need this because the rate can theoretically be negative + + void setState(QMediaPlayer::State state); + void setMediaStatus(QMediaPlayer::MediaStatus status); + void setSeekable(bool seekable); + void setAudioAvailable(bool available); + void setVideoAvailable(bool available); + void updateAvailablePlaybackRanges(); + void resetBufferingProgress(); + void flushPendingStates(); + void updateBufferStatus(); + + friend class StateChangeNotifier; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIAPLAYERCONTROL_H diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol.cpp new file mode 100644 index 000000000..aed7ba671 --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol.cpp @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** 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 "qandroidmediaplayervideorenderercontrol_p.h" + +#include "qandroidmediaplayercontrol_p.h" +#include "qandroidvideooutput_p.h" +#include <qabstractvideosurface.h> + +QT_BEGIN_NAMESPACE + +QAndroidMediaPlayerVideoRendererControl::QAndroidMediaPlayerVideoRendererControl(QAndroidMediaPlayerControl *mediaPlayer, QObject *parent) + : QVideoRendererControl(parent) + , m_mediaPlayerControl(mediaPlayer) + , m_surface(0) + , m_textureOutput(new QAndroidTextureVideoOutput(this)) +{ + m_mediaPlayerControl->setVideoOutput(m_textureOutput); +} + +QAndroidMediaPlayerVideoRendererControl::~QAndroidMediaPlayerVideoRendererControl() +{ + m_mediaPlayerControl->setVideoOutput(0); +} + +QAbstractVideoSurface *QAndroidMediaPlayerVideoRendererControl::surface() const +{ + return m_surface; +} + +void QAndroidMediaPlayerVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface == surface) + return; + + m_surface = surface; + m_textureOutput->setSurface(m_surface); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol_p.h new file mode 100644 index 000000000..91aed30fb --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaplayervideorenderercontrol_p.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIAPLAYERVIDEORENDERERCONTROL_H +#define QANDROIDMEDIAPLAYERVIDEORENDERERCONTROL_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 <qvideorenderercontrol.h> + +QT_BEGIN_NAMESPACE + +class QAndroidMediaPlayerControl; +class QAndroidTextureVideoOutput; + +class QAndroidMediaPlayerVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + QAndroidMediaPlayerVideoRendererControl(QAndroidMediaPlayerControl *mediaPlayer, QObject *parent = 0); + ~QAndroidMediaPlayerVideoRendererControl() override; + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + +private: + QAndroidMediaPlayerControl *m_mediaPlayerControl; + QAbstractVideoSurface *m_surface; + QAndroidTextureVideoOutput *m_textureOutput; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIAPLAYERVIDEORENDERERCONTROL_H diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaservice.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmediaservice.cpp new file mode 100644 index 000000000..7ccf1dc3a --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaservice.cpp @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 "qandroidmediaservice_p.h" + +#include "qandroidmediaplayercontrol_p.h" +#include "qandroidmetadatareadercontrol_p.h" +#include "qandroidmediaplayervideorenderercontrol_p.h" + +QT_BEGIN_NAMESPACE + +QAndroidMediaService::QAndroidMediaService(QObject *parent) + : QMediaService(parent) + , mAudioRoleControl(nullptr) + , mCustomAudioRoleControl(nullptr) + , mVideoRendererControl(0) +{ + mMediaControl = new QAndroidMediaPlayerControl; + mMetadataControl = new QAndroidMetaDataReaderControl; + connect(mMediaControl, SIGNAL(mediaChanged(QUrl)), + mMetadataControl, SLOT(onMediaChanged(QUrl))); + connect(mMediaControl, SIGNAL(metaDataUpdated()), + mMetadataControl, SLOT(onUpdateMetaData())); +} + +QAndroidMediaService::~QAndroidMediaService() +{ + delete mVideoRendererControl; + delete mMetadataControl; + delete mMediaControl; +} + +QObject *QAndroidMediaService::requestControl(const char *name) +{ + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) + return mMediaControl; + + if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) + return mMetadataControl; + + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (!mVideoRendererControl) { + mVideoRendererControl = new QAndroidMediaPlayerVideoRendererControl(mMediaControl); + return mVideoRendererControl; + } + } + + return 0; +} + +void QAndroidMediaService::releaseControl(QObject *control) +{ + if (control == mVideoRendererControl) { + delete mVideoRendererControl; + mVideoRendererControl = 0; + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmediaservice_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmediaservice_p.h new file mode 100644 index 000000000..984c95ba3 --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmediaservice_p.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIASERVICE_H +#define QANDROIDMEDIASERVICE_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 <QMediaService> + +QT_BEGIN_NAMESPACE + +class QAndroidMediaPlayerControl; +class QAndroidMetaDataReaderControl; +class QAndroidAudioRoleControl; +class QAndroidCustomAudioRoleControl; +class QAndroidMediaPlayerVideoRendererControl; + +class QAndroidMediaService : public QMediaService +{ + Q_OBJECT +public: + explicit QAndroidMediaService(QObject *parent = 0); + ~QAndroidMediaService() override; + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *control) override; + +private: + QAndroidMediaPlayerControl *mMediaControl; + QAndroidMetaDataReaderControl *mMetadataControl; + QAndroidAudioRoleControl *mAudioRoleControl; + QAndroidCustomAudioRoleControl *mCustomAudioRoleControl; + QAndroidMediaPlayerVideoRendererControl *mVideoRendererControl; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIASERVICE_H diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol.cpp b/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol.cpp new file mode 100644 index 000000000..31263335b --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** 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 "qandroidmetadatareadercontrol_p.h" + +#include "androidmediametadataretriever_p.h" +#include <QtMultimedia/qmediametadata.h> +#include <qsize.h> +#include <QDate> +#include <QtCore/qlist.h> +#include <QtConcurrent/qtconcurrentrun.h> + +QT_BEGIN_NAMESPACE + +// Genre name ordered by ID +// see: http://id3.org/id3v2.3.0#Appendix_A_-_Genre_List_from_ID3v1 +static const char* qt_ID3GenreNames[] = +{ + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge", "Hip-Hop", "Jazz", + "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", "Techno", + "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", + "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental", + "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", "Soul", "Punk", + "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", + "Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy", + "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", + "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", + "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", + "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob", "Latin", "Revival", "Celtic", + "Bluegrass", "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", + "Symphonic Rock", "Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", + "Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", "Folklore", "Ballad", + "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", + "Euro-House", "Dance Hall" +}; + +typedef QList<QAndroidMetaDataReaderControl *> AndroidMetaDataReaders; +Q_GLOBAL_STATIC(AndroidMetaDataReaders, g_metaDataReaders) +Q_GLOBAL_STATIC(QMutex, g_metaDataReadersMtx) + +QAndroidMetaDataReaderControl::QAndroidMetaDataReaderControl(QObject *parent) + : QMetaDataReaderControl(parent) + , m_available(false) +{ +} + +QAndroidMetaDataReaderControl::~QAndroidMetaDataReaderControl() +{ + QMutexLocker l(g_metaDataReadersMtx()); + const int idx = g_metaDataReaders->indexOf(this); + if (idx != -1) + g_metaDataReaders->remove(idx); +} + +bool QAndroidMetaDataReaderControl::isMetaDataAvailable() const +{ + const QMutexLocker l(&m_mtx); + return m_available && !m_metadata.isEmpty(); +} + +QVariant QAndroidMetaDataReaderControl::metaData(const QString &key) const +{ + const QMutexLocker l(&m_mtx); + return m_metadata.value(key); +} + +QStringList QAndroidMetaDataReaderControl::availableMetaData() const +{ + const QMutexLocker l(&m_mtx); + return m_metadata.keys(); +} + +void QAndroidMetaDataReaderControl::onMediaChanged(const QUrl &media) +{ + const QMutexLocker l(&m_mtx); + m_metadata.clear(); + m_mediaContent = media; +} + +void QAndroidMetaDataReaderControl::onUpdateMetaData() +{ + { + const QMutexLocker l(g_metaDataReadersMtx()); + if (!g_metaDataReaders->contains(this)) + g_metaDataReaders->append(this); + } + + const QMutexLocker ml(&m_mtx); + if (m_mediaContent.isNull()) + return; + + const QUrl &url = m_mediaContent.request().url(); + QtConcurrent::run(&extractMetadata, this, url); +} + +void QAndroidMetaDataReaderControl::updateData(const QVariantMap &metadata, const QUrl &url) +{ + const QMutexLocker l(&m_mtx); + + if (m_mediaContent.request().url() != url) + return; + + const bool oldAvailable = m_available; + m_metadata = metadata; + m_available = !m_metadata.isEmpty(); + + if (m_available != oldAvailable) + Q_EMIT metaDataAvailableChanged(m_available); + + Q_EMIT metaDataChanged(); +} + +void QAndroidMetaDataReaderControl::extractMetadata(QAndroidMetaDataReaderControl *caller, + const QUrl &url) +{ + QVariantMap metadata; + + if (!url.isEmpty()) { + AndroidMediaMetadataRetriever retriever; + if (!retriever.setDataSource(url)) + return; + + QString mimeType = retriever.extractMetadata(AndroidMediaMetadataRetriever::MimeType); + if (!mimeType.isNull()) + metadata.insert(QMediaMetaData::MediaType, mimeType); + + bool isVideo = !retriever.extractMetadata(AndroidMediaMetadataRetriever::HasVideo).isNull() + || mimeType.startsWith(QStringLiteral("video")); + + QString string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Album); + if (!string.isNull()) + metadata.insert(QMediaMetaData::AlbumTitle, string); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::AlbumArtist); + if (!string.isNull()) + metadata.insert(QMediaMetaData::AlbumArtist, string); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Artist); + if (!string.isNull()) { + metadata.insert(isVideo ? QMediaMetaData::LeadPerformer + : QMediaMetaData::ContributingArtist, + string.split('/', Qt::SkipEmptyParts)); + } + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Author); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Author, string.split('/', Qt::SkipEmptyParts)); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Bitrate); + if (!string.isNull()) { + metadata.insert(isVideo ? QMediaMetaData::VideoBitRate + : QMediaMetaData::AudioBitRate, + string.toInt()); + } + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::CDTrackNumber); + if (!string.isNull()) + metadata.insert(QMediaMetaData::TrackNumber, string.toInt()); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Composer); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Composer, string.split('/', Qt::SkipEmptyParts)); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Date); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Date, QDateTime::fromString(string, QStringLiteral("yyyyMMddTHHmmss.zzzZ")).date()); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Duration); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Duration, string.toLongLong()); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Genre); + if (!string.isNull()) { + // The genre can be returned as an ID3v2 id, get the name for it in that case + if (string.startsWith('(') && string.endsWith(')')) { + bool ok = false; + const int genreId = QStringView{string}.mid(1, string.length() - 2).toInt(&ok); + if (ok && genreId >= 0 && genreId <= 125) + string = QLatin1String(qt_ID3GenreNames[genreId]); + } + metadata.insert(QMediaMetaData::Genre, string); + } + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Title); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Title, string); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::VideoHeight); + if (!string.isNull()) { + const int height = string.toInt(); + const int width = retriever.extractMetadata(AndroidMediaMetadataRetriever::VideoWidth).toInt(); + metadata.insert(QMediaMetaData::Resolution, QSize(width, height)); + } + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Writer); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Writer, string.split('/', Qt::SkipEmptyParts)); + + string = retriever.extractMetadata(AndroidMediaMetadataRetriever::Year); + if (!string.isNull()) + metadata.insert(QMediaMetaData::Year, string.toInt()); + } + + const QMutexLocker lock(g_metaDataReadersMtx()); + if (!g_metaDataReaders->contains(caller)) + return; + + caller->updateData(metadata, url); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol_p.h b/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol_p.h new file mode 100644 index 000000000..2ee857fa1 --- /dev/null +++ b/src/multimedia/platform/android/mediaplayer/qandroidmetadatareadercontrol_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** 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 QANDROIDMETADATAREADERCONTROL_H +#define QANDROIDMETADATAREADERCONTROL_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 <QMetaDataReaderControl> +#include <QUrl.h> +#include <QMutex> + +QT_BEGIN_NAMESPACE + +class AndroidMediaMetadataRetriever; + +class QAndroidMetaDataReaderControl : public QMetaDataReaderControl +{ + Q_OBJECT +public: + explicit QAndroidMetaDataReaderControl(QObject *parent = 0); + ~QAndroidMetaDataReaderControl() override; + + bool isMetaDataAvailable() const override; + + QVariant metaData(const QString &key) const override; + QStringList availableMetaData() const override; + +public Q_SLOTS: + void onMediaChanged(const QUrl &media); + void onUpdateMetaData(); + +private: + void updateData(const QVariantMap &metadata, const QUrl &url); + static void extractMetadata(QAndroidMetaDataReaderControl *caller, const QUrl &url); + + mutable QMutex m_mtx; + QUrl m_mediaContent; + bool m_available; + QVariantMap m_metadata; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMETADATAREADERCONTROL_H diff --git a/src/multimedia/platform/android/qandroidmediaserviceplugin.cpp b/src/multimedia/platform/android/qandroidmediaserviceplugin.cpp new file mode 100644 index 000000000..52cedb5bd --- /dev/null +++ b/src/multimedia/platform/android/qandroidmediaserviceplugin.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** 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 "qandroidmediaserviceplugin_p.h" + +#include "qandroidmediaservice_p.h" +#include "qandroidcaptureservice_p.h" +#include "qandroidaudioinputselectorcontrol_p.h" +#include "qandroidcamerasession_p.h" +#include "androidmediaplayer_p.h" +#include "androidsurfacetexture_p.h" +#include "androidcamera_p.h" +#include "androidmultimediautils_p.h" +#include "androidmediarecorder_p.h" +#include "androidsurfaceview_p.h" +#include "qandroidglobal_p.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qtAndroidMediaPlugin, "qt.multimedia.android") + +QAndroidMediaServicePlugin::QAndroidMediaServicePlugin() +{ +} + +QAndroidMediaServicePlugin::~QAndroidMediaServicePlugin() +{ +} + +QMediaService *QAndroidMediaServicePlugin::create(const QString &key) +{ + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new QAndroidMediaService; + + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA) + || key == QLatin1String(Q_MEDIASERVICE_AUDIOSOURCE)) { + return new QAndroidCaptureService(key); + } + + qCWarning(qtAndroidMediaPlugin) << "Android service plugin: unsupported key:" << key; + return 0; +} + +void QAndroidMediaServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QByteArray QAndroidMediaServicePlugin::defaultDevice(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA && !QAndroidCameraSession::availableCameras().isEmpty()) + return QAndroidCameraSession::availableCameras().first().name; + + return QByteArray(); +} + +QList<QByteArray> QAndroidMediaServicePlugin::devices(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA) { + QList<QByteArray> devices; + const QList<AndroidCameraInfo> &cameras = QAndroidCameraSession::availableCameras(); + for (int i = 0; i < cameras.count(); ++i) + devices.append(cameras.at(i).name); + return devices; + } + + if (service == Q_MEDIASERVICE_AUDIOSOURCE) + return QAndroidAudioInputSelectorControl::availableDevices(); + + return QList<QByteArray>(); +} + +QString QAndroidMediaServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + if (service == Q_MEDIASERVICE_CAMERA) { + const QList<AndroidCameraInfo> &cameras = QAndroidCameraSession::availableCameras(); + for (int i = 0; i < cameras.count(); ++i) { + const AndroidCameraInfo &info = cameras.at(i); + if (info.name == device) + return info.description; + } + } + + if (service == Q_MEDIASERVICE_AUDIOSOURCE) + return QAndroidAudioInputSelectorControl::availableDeviceDescription(device); + + return QString(); +} + +QT_END_NAMESPACE + +Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /*reserved*/) +{ + static bool initialized = false; + if (initialized) + return JNI_VERSION_1_6; + initialized = true; + + QT_USE_NAMESPACE + typedef union { + JNIEnv *nativeEnvironment; + void *venv; + } UnionJNIEnvToVoid; + + UnionJNIEnvToVoid uenv; + uenv.venv = NULL; + + if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) + return JNI_ERR; + + JNIEnv *jniEnv = uenv.nativeEnvironment; + + if (!AndroidMediaPlayer::initJNI(jniEnv) || + !AndroidCamera::initJNI(jniEnv) || + !AndroidMediaRecorder::initJNI(jniEnv) || + !AndroidSurfaceHolder::initJNI(jniEnv)) { + return JNI_ERR; + } + + AndroidSurfaceTexture::initJNI(jniEnv); + + return JNI_VERSION_1_6; +} diff --git a/src/multimedia/platform/android/qandroidmediaserviceplugin_p.h b/src/multimedia/platform/android/qandroidmediaserviceplugin_p.h new file mode 100644 index 000000000..8c024cad7 --- /dev/null +++ b/src/multimedia/platform/android/qandroidmediaserviceplugin_p.h @@ -0,0 +1,81 @@ +/**************************************************************************** +** +** 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 QANDROIDMEDIASERVICEPLUGIN_H +#define QANDROIDMEDIASERVICEPLUGIN_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 <QMediaServiceProviderPlugin> + +QT_BEGIN_NAMESPACE + +class QAndroidMediaServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedDevicesInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" + FILE "android_mediaservice.json") + +public: + QAndroidMediaServicePlugin(); + ~QAndroidMediaServicePlugin(); + + QMediaService* create(QString const& key) override; + void release(QMediaService *service) override; + + QByteArray defaultDevice(const QByteArray &service) const override; + QList<QByteArray> devices(const QByteArray &service) const override; + QString deviceDescription(const QByteArray &service, const QByteArray &device) override; +}; + +QT_END_NAMESPACE + +#endif // QANDROIDMEDIASERVICEPLUGIN_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidcamera.cpp b/src/multimedia/platform/android/wrappers/jni/androidcamera.cpp new file mode 100644 index 000000000..60fc4ec8a --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidcamera.cpp @@ -0,0 +1,1712 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 "androidcamera_p.h" +#include "androidsurfacetexture_p.h" +#include "androidsurfaceview_p.h" +#include "qandroidmultimediautils_p.h" +#include "qandroidglobal_p.h" + +#include <qstringlist.h> +#include <qdebug.h> +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/qthread.h> +#include <QtCore/qreadwritelock.h> +#include <QtCore/qmutex.h> +#include <QtMultimedia/private/qmemoryvideobuffer_p.h> + +#include <mutex> + +QT_BEGIN_NAMESPACE + +static const char QtCameraListenerClassName[] = "org/qtproject/qt/android/multimedia/QtCameraListener"; + +typedef QHash<int, AndroidCamera *> CameraMap; +Q_GLOBAL_STATIC(CameraMap, cameras) +Q_GLOBAL_STATIC(QReadWriteLock, rwLock) + +static inline bool exceptionCheckAndClear(JNIEnv *env) +{ + if (Q_UNLIKELY(env->ExceptionCheck())) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + return true; + } + + return false; +} + +static QRect areaToRect(jobject areaObj) +{ + QJNIObjectPrivate area(areaObj); + QJNIObjectPrivate rect = area.getObjectField("rect", "Landroid/graphics/Rect;"); + + return QRect(rect.getField<jint>("left"), + rect.getField<jint>("top"), + rect.callMethod<jint>("width"), + rect.callMethod<jint>("height")); +} + +static QJNIObjectPrivate rectToArea(const QRect &rect) +{ + QJNIObjectPrivate jrect("android/graphics/Rect", + "(IIII)V", + rect.left(), rect.top(), rect.right(), rect.bottom()); + + QJNIObjectPrivate area("android/hardware/Camera$Area", + "(Landroid/graphics/Rect;I)V", + jrect.object(), 500); + + return area; +} + +// native method for QtCameraLisener.java +static void notifyAutoFocusComplete(JNIEnv* , jobject, int id, jboolean success) +{ + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; + + Q_EMIT (*it)->autoFocusComplete(success); +} + +static void notifyPictureExposed(JNIEnv* , jobject, int id) +{ + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; + + Q_EMIT (*it)->pictureExposed(); +} + +static void notifyPictureCaptured(JNIEnv *env, jobject, int id, jbyteArray data) +{ + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; + + const int arrayLength = env->GetArrayLength(data); + QByteArray bytes(arrayLength, Qt::Uninitialized); + env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); + Q_EMIT (*it)->pictureCaptured(bytes); +} + +static void notifyNewPreviewFrame(JNIEnv *env, jobject, int id, jbyteArray data, + int width, int height, int format, int bpl) +{ + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; + + const int arrayLength = env->GetArrayLength(data); + if (arrayLength == 0) + return; + + QByteArray bytes(arrayLength, Qt::Uninitialized); + env->GetByteArrayRegion(data, 0, arrayLength, (jbyte*)bytes.data()); + + QVideoFrame frame(new QMemoryVideoBuffer(bytes, bpl), + QSize(width, height), + qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format))); + + Q_EMIT (*it)->newPreviewFrame(frame); +} + +static void notifyFrameAvailable(JNIEnv *, jobject, int id) +{ + QReadLocker locker(rwLock); + const auto it = cameras->constFind(id); + if (Q_UNLIKELY(it == cameras->cend())) + return; + + (*it)->fetchLastPreviewFrame(); +} + +class AndroidCameraPrivate : public QObject +{ + Q_OBJECT +public: + AndroidCameraPrivate(); + ~AndroidCameraPrivate(); + + Q_INVOKABLE bool init(int cameraId); + + Q_INVOKABLE void release(); + Q_INVOKABLE bool lock(); + Q_INVOKABLE bool unlock(); + Q_INVOKABLE bool reconnect(); + + Q_INVOKABLE AndroidCamera::CameraFacing getFacing(); + Q_INVOKABLE int getNativeOrientation(); + + Q_INVOKABLE QSize getPreferredPreviewSizeForVideo(); + Q_INVOKABLE QList<QSize> getSupportedPreviewSizes(); + + Q_INVOKABLE QList<AndroidCamera::FpsRange> getSupportedPreviewFpsRange(); + + Q_INVOKABLE AndroidCamera::FpsRange getPreviewFpsRange(); + Q_INVOKABLE void setPreviewFpsRange(int min, int max); + + Q_INVOKABLE AndroidCamera::ImageFormat getPreviewFormat(); + Q_INVOKABLE void setPreviewFormat(AndroidCamera::ImageFormat fmt); + Q_INVOKABLE QList<AndroidCamera::ImageFormat> getSupportedPreviewFormats(); + + Q_INVOKABLE QSize previewSize() const { return m_previewSize; } + Q_INVOKABLE QSize getPreviewSize(); + Q_INVOKABLE void updatePreviewSize(); + Q_INVOKABLE bool setPreviewTexture(void *surfaceTexture); + Q_INVOKABLE bool setPreviewDisplay(void *surfaceHolder); + Q_INVOKABLE void setDisplayOrientation(int degrees); + + Q_INVOKABLE bool isZoomSupported(); + Q_INVOKABLE int getMaxZoom(); + Q_INVOKABLE QList<int> getZoomRatios(); + Q_INVOKABLE int getZoom(); + Q_INVOKABLE void setZoom(int value); + + Q_INVOKABLE QString getFlashMode(); + Q_INVOKABLE void setFlashMode(const QString &value); + + Q_INVOKABLE QString getFocusMode(); + Q_INVOKABLE void setFocusMode(const QString &value); + + Q_INVOKABLE int getMaxNumFocusAreas(); + Q_INVOKABLE QList<QRect> getFocusAreas(); + Q_INVOKABLE void setFocusAreas(const QList<QRect> &areas); + + Q_INVOKABLE void autoFocus(); + Q_INVOKABLE void cancelAutoFocus(); + + Q_INVOKABLE bool isAutoExposureLockSupported(); + Q_INVOKABLE bool getAutoExposureLock(); + Q_INVOKABLE void setAutoExposureLock(bool toggle); + + Q_INVOKABLE bool isAutoWhiteBalanceLockSupported(); + Q_INVOKABLE bool getAutoWhiteBalanceLock(); + Q_INVOKABLE void setAutoWhiteBalanceLock(bool toggle); + + Q_INVOKABLE int getExposureCompensation(); + Q_INVOKABLE void setExposureCompensation(int value); + Q_INVOKABLE float getExposureCompensationStep(); + Q_INVOKABLE int getMinExposureCompensation(); + Q_INVOKABLE int getMaxExposureCompensation(); + + Q_INVOKABLE QString getSceneMode(); + Q_INVOKABLE void setSceneMode(const QString &value); + + Q_INVOKABLE QString getWhiteBalance(); + Q_INVOKABLE void setWhiteBalance(const QString &value); + + Q_INVOKABLE void updateRotation(); + + Q_INVOKABLE QList<QSize> getSupportedPictureSizes(); + Q_INVOKABLE void setPictureSize(const QSize &size); + Q_INVOKABLE void setJpegQuality(int quality); + + Q_INVOKABLE void startPreview(); + Q_INVOKABLE void stopPreview(); + + Q_INVOKABLE void takePicture(); + + Q_INVOKABLE void setupPreviewFrameCallback(); + Q_INVOKABLE void notifyNewFrames(bool notify); + Q_INVOKABLE void fetchLastPreviewFrame(); + + Q_INVOKABLE void applyParameters(); + + Q_INVOKABLE QStringList callParametersStringListMethod(const QByteArray &methodName); + + int m_cameraId; + QRecursiveMutex m_parametersMutex; + QSize m_previewSize; + int m_rotation; + QJNIObjectPrivate m_info; + QJNIObjectPrivate m_parameters; + QJNIObjectPrivate m_camera; + QJNIObjectPrivate m_cameraListener; + +Q_SIGNALS: + void previewSizeChanged(); + void previewStarted(); + void previewFailedToStart(); + void previewStopped(); + + void autoFocusStarted(); + + void whiteBalanceChanged(); + + void takePictureFailed(); + + void lastPreviewFrameFetched(const QVideoFrame &frame); +}; + +AndroidCamera::AndroidCamera(AndroidCameraPrivate *d, QThread *worker) + : QObject(), + d_ptr(d), + m_worker(worker) + +{ + qRegisterMetaType<QList<int> >(); + qRegisterMetaType<QList<QSize> >(); + qRegisterMetaType<QList<QRect> >(); + qRegisterMetaType<ImageFormat>(); + + connect(d, &AndroidCameraPrivate::previewSizeChanged, this, &AndroidCamera::previewSizeChanged); + connect(d, &AndroidCameraPrivate::previewStarted, this, &AndroidCamera::previewStarted); + connect(d, &AndroidCameraPrivate::previewFailedToStart, this, &AndroidCamera::previewFailedToStart); + connect(d, &AndroidCameraPrivate::previewStopped, this, &AndroidCamera::previewStopped); + connect(d, &AndroidCameraPrivate::autoFocusStarted, this, &AndroidCamera::autoFocusStarted); + connect(d, &AndroidCameraPrivate::whiteBalanceChanged, this, &AndroidCamera::whiteBalanceChanged); + connect(d, &AndroidCameraPrivate::takePictureFailed, this, &AndroidCamera::takePictureFailed); + connect(d, &AndroidCameraPrivate::lastPreviewFrameFetched, this, &AndroidCamera::lastPreviewFrameFetched); +} + +AndroidCamera::~AndroidCamera() +{ + Q_D(AndroidCamera); + if (d->m_camera.isValid()) { + release(); + QWriteLocker locker(rwLock); + cameras->remove(cameraId()); + } + + m_worker->exit(); + m_worker->wait(5000); +} + +AndroidCamera *AndroidCamera::open(int cameraId) +{ + if (!qt_androidRequestCameraPermission()) + return nullptr; + + AndroidCameraPrivate *d = new AndroidCameraPrivate(); + QThread *worker = new QThread; + worker->start(); + d->moveToThread(worker); + connect(worker, &QThread::finished, d, &AndroidCameraPrivate::deleteLater); + bool ok = true; + QMetaObject::invokeMethod(d, "init", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok), Q_ARG(int, cameraId)); + if (!ok) { + worker->quit(); + worker->wait(5000); + delete worker; + return 0; + } + + AndroidCamera *q = new AndroidCamera(d, worker); + QWriteLocker locker(rwLock); + cameras->insert(cameraId, q); + + return q; +} + +int AndroidCamera::cameraId() const +{ + Q_D(const AndroidCamera); + return d->m_cameraId; +} + +bool AndroidCamera::lock() +{ + Q_D(AndroidCamera); + bool ok = true; + QMetaObject::invokeMethod(d, "lock", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); + return ok; +} + +bool AndroidCamera::unlock() +{ + Q_D(AndroidCamera); + bool ok = true; + QMetaObject::invokeMethod(d, "unlock", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); + return ok; +} + +bool AndroidCamera::reconnect() +{ + Q_D(AndroidCamera); + bool ok = true; + QMetaObject::invokeMethod(d, "reconnect", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, ok)); + return ok; +} + +void AndroidCamera::release() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "release", Qt::BlockingQueuedConnection); +} + +AndroidCamera::CameraFacing AndroidCamera::getFacing() +{ + Q_D(AndroidCamera); + return d->getFacing(); +} + +int AndroidCamera::getNativeOrientation() +{ + Q_D(AndroidCamera); + return d->getNativeOrientation(); +} + +QSize AndroidCamera::getPreferredPreviewSizeForVideo() +{ + Q_D(AndroidCamera); + return d->getPreferredPreviewSizeForVideo(); +} + +QList<QSize> AndroidCamera::getSupportedPreviewSizes() +{ + Q_D(AndroidCamera); + return d->getSupportedPreviewSizes(); +} + +QList<AndroidCamera::FpsRange> AndroidCamera::getSupportedPreviewFpsRange() +{ + Q_D(AndroidCamera); + return d->getSupportedPreviewFpsRange(); +} + +AndroidCamera::FpsRange AndroidCamera::getPreviewFpsRange() +{ + Q_D(AndroidCamera); + return d->getPreviewFpsRange(); +} + +void AndroidCamera::setPreviewFpsRange(FpsRange range) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setPreviewFpsRange", Q_ARG(int, range.min), Q_ARG(int, range.max)); +} + +AndroidCamera::ImageFormat AndroidCamera::getPreviewFormat() +{ + Q_D(AndroidCamera); + return d->getPreviewFormat(); +} + +void AndroidCamera::setPreviewFormat(ImageFormat fmt) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setPreviewFormat", Q_ARG(AndroidCamera::ImageFormat, fmt)); +} + +QList<AndroidCamera::ImageFormat> AndroidCamera::getSupportedPreviewFormats() +{ + Q_D(AndroidCamera); + return d->getSupportedPreviewFormats(); +} + +QSize AndroidCamera::previewSize() const +{ + Q_D(const AndroidCamera); + return d->m_previewSize; +} + +QSize AndroidCamera::actualPreviewSize() +{ + Q_D(AndroidCamera); + return d->getPreviewSize(); +} + +void AndroidCamera::setPreviewSize(const QSize &size) +{ + Q_D(AndroidCamera); + d->m_parametersMutex.lock(); + bool areParametersValid = d->m_parameters.isValid(); + d->m_parametersMutex.unlock(); + if (!areParametersValid) + return; + + d->m_previewSize = size; + QMetaObject::invokeMethod(d, "updatePreviewSize"); +} + +bool AndroidCamera::setPreviewTexture(AndroidSurfaceTexture *surfaceTexture) +{ + Q_D(AndroidCamera); + bool ok = true; + QMetaObject::invokeMethod(d, + "setPreviewTexture", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ok), + Q_ARG(void *, surfaceTexture ? surfaceTexture->surfaceTexture() : 0)); + return ok; +} + +bool AndroidCamera::setPreviewDisplay(AndroidSurfaceHolder *surfaceHolder) +{ + Q_D(AndroidCamera); + bool ok = true; + QMetaObject::invokeMethod(d, + "setPreviewDisplay", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, ok), + Q_ARG(void *, surfaceHolder ? surfaceHolder->surfaceHolder() : 0)); + return ok; +} + +void AndroidCamera::setDisplayOrientation(int degrees) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setDisplayOrientation", Qt::QueuedConnection, Q_ARG(int, degrees)); +} + +bool AndroidCamera::isZoomSupported() +{ + Q_D(AndroidCamera); + return d->isZoomSupported(); +} + +int AndroidCamera::getMaxZoom() +{ + Q_D(AndroidCamera); + return d->getMaxZoom(); +} + +QList<int> AndroidCamera::getZoomRatios() +{ + Q_D(AndroidCamera); + return d->getZoomRatios(); +} + +int AndroidCamera::getZoom() +{ + Q_D(AndroidCamera); + return d->getZoom(); +} + +void AndroidCamera::setZoom(int value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setZoom", Q_ARG(int, value)); +} + +QStringList AndroidCamera::getSupportedFlashModes() +{ + Q_D(AndroidCamera); + return d->callParametersStringListMethod("getSupportedFlashModes"); +} + +QString AndroidCamera::getFlashMode() +{ + Q_D(AndroidCamera); + return d->getFlashMode(); +} + +void AndroidCamera::setFlashMode(const QString &value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setFlashMode", Q_ARG(QString, value)); +} + +QStringList AndroidCamera::getSupportedFocusModes() +{ + Q_D(AndroidCamera); + return d->callParametersStringListMethod("getSupportedFocusModes"); +} + +QString AndroidCamera::getFocusMode() +{ + Q_D(AndroidCamera); + return d->getFocusMode(); +} + +void AndroidCamera::setFocusMode(const QString &value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setFocusMode", Q_ARG(QString, value)); +} + +int AndroidCamera::getMaxNumFocusAreas() +{ + Q_D(AndroidCamera); + return d->getMaxNumFocusAreas(); +} + +QList<QRect> AndroidCamera::getFocusAreas() +{ + Q_D(AndroidCamera); + return d->getFocusAreas(); +} + +void AndroidCamera::setFocusAreas(const QList<QRect> &areas) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setFocusAreas", Q_ARG(QList<QRect>, areas)); +} + +void AndroidCamera::autoFocus() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "autoFocus"); +} + +void AndroidCamera::cancelAutoFocus() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "cancelAutoFocus", Qt::QueuedConnection); +} + +bool AndroidCamera::isAutoExposureLockSupported() +{ + Q_D(AndroidCamera); + return d->isAutoExposureLockSupported(); +} + +bool AndroidCamera::getAutoExposureLock() +{ + Q_D(AndroidCamera); + return d->getAutoExposureLock(); +} + +void AndroidCamera::setAutoExposureLock(bool toggle) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setAutoExposureLock", Q_ARG(bool, toggle)); +} + +bool AndroidCamera::isAutoWhiteBalanceLockSupported() +{ + Q_D(AndroidCamera); + return d->isAutoWhiteBalanceLockSupported(); +} + +bool AndroidCamera::getAutoWhiteBalanceLock() +{ + Q_D(AndroidCamera); + return d->getAutoWhiteBalanceLock(); +} + +void AndroidCamera::setAutoWhiteBalanceLock(bool toggle) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setAutoWhiteBalanceLock", Q_ARG(bool, toggle)); +} + +int AndroidCamera::getExposureCompensation() +{ + Q_D(AndroidCamera); + return d->getExposureCompensation(); +} + +void AndroidCamera::setExposureCompensation(int value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setExposureCompensation", Q_ARG(int, value)); +} + +float AndroidCamera::getExposureCompensationStep() +{ + Q_D(AndroidCamera); + return d->getExposureCompensationStep(); +} + +int AndroidCamera::getMinExposureCompensation() +{ + Q_D(AndroidCamera); + return d->getMinExposureCompensation(); +} + +int AndroidCamera::getMaxExposureCompensation() +{ + Q_D(AndroidCamera); + return d->getMaxExposureCompensation(); +} + +QStringList AndroidCamera::getSupportedSceneModes() +{ + Q_D(AndroidCamera); + return d->callParametersStringListMethod("getSupportedSceneModes"); +} + +QString AndroidCamera::getSceneMode() +{ + Q_D(AndroidCamera); + return d->getSceneMode(); +} + +void AndroidCamera::setSceneMode(const QString &value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setSceneMode", Q_ARG(QString, value)); +} + +QStringList AndroidCamera::getSupportedWhiteBalance() +{ + Q_D(AndroidCamera); + return d->callParametersStringListMethod("getSupportedWhiteBalance"); +} + +QString AndroidCamera::getWhiteBalance() +{ + Q_D(AndroidCamera); + return d->getWhiteBalance(); +} + +void AndroidCamera::setWhiteBalance(const QString &value) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setWhiteBalance", Q_ARG(QString, value)); +} + +void AndroidCamera::setRotation(int rotation) +{ + Q_D(AndroidCamera); + //We need to do it here and not in worker class because we cache rotation + d->m_parametersMutex.lock(); + bool areParametersValid = d->m_parameters.isValid(); + d->m_parametersMutex.unlock(); + if (!areParametersValid) + return; + + d->m_rotation = rotation; + QMetaObject::invokeMethod(d, "updateRotation"); +} + +int AndroidCamera::getRotation() const +{ + Q_D(const AndroidCamera); + return d->m_rotation; +} + +QList<QSize> AndroidCamera::getSupportedPictureSizes() +{ + Q_D(AndroidCamera); + return d->getSupportedPictureSizes(); +} + +void AndroidCamera::setPictureSize(const QSize &size) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setPictureSize", Q_ARG(QSize, size)); +} + +void AndroidCamera::setJpegQuality(int quality) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setJpegQuality", Q_ARG(int, quality)); +} + +void AndroidCamera::takePicture() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "takePicture", Qt::BlockingQueuedConnection); +} + +void AndroidCamera::setupPreviewFrameCallback() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "setupPreviewFrameCallback"); +} + +void AndroidCamera::notifyNewFrames(bool notify) +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "notifyNewFrames", Q_ARG(bool, notify)); +} + +void AndroidCamera::fetchLastPreviewFrame() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "fetchLastPreviewFrame"); +} + +QJNIObjectPrivate AndroidCamera::getCameraObject() +{ + Q_D(AndroidCamera); + return d->m_camera; +} + +int AndroidCamera::getNumberOfCameras() +{ + if (!qt_androidRequestCameraPermission()) + return 0; + + return QJNIObjectPrivate::callStaticMethod<jint>("android/hardware/Camera", + "getNumberOfCameras"); +} + +void AndroidCamera::getCameraInfo(int id, AndroidCameraInfo *info) +{ + Q_ASSERT(info); + + QJNIObjectPrivate cameraInfo("android/hardware/Camera$CameraInfo"); + QJNIObjectPrivate::callStaticMethod<void>("android/hardware/Camera", + "getCameraInfo", + "(ILandroid/hardware/Camera$CameraInfo;)V", + id, cameraInfo.object()); + + AndroidCamera::CameraFacing facing = AndroidCamera::CameraFacing(cameraInfo.getField<jint>("facing")); + // The orientation provided by Android is counter-clockwise, we need it clockwise + info->orientation = (360 - cameraInfo.getField<jint>("orientation")) % 360; + + switch (facing) { + case AndroidCamera::CameraFacingBack: + info->name = QByteArray("back"); + info->description = QStringLiteral("Rear-facing camera"); + info->position = QCamera::BackFace; + break; + case AndroidCamera::CameraFacingFront: + info->name = QByteArray("front"); + info->description = QStringLiteral("Front-facing camera"); + info->position = QCamera::FrontFace; + break; + default: + break; + } +} + +void AndroidCamera::startPreview() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "startPreview"); +} + +void AndroidCamera::stopPreview() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "stopPreview"); +} + +void AndroidCamera::stopPreviewSynchronous() +{ + Q_D(AndroidCamera); + QMetaObject::invokeMethod(d, "stopPreview", Qt::BlockingQueuedConnection); +} + +AndroidCameraPrivate::AndroidCameraPrivate() + : QObject() +{ +} + +AndroidCameraPrivate::~AndroidCameraPrivate() +{ +} + +static qint32 s_activeCameras = 0; + +bool AndroidCameraPrivate::init(int cameraId) +{ + m_cameraId = cameraId; + QJNIEnvironmentPrivate env; + + const bool opened = s_activeCameras & (1 << cameraId); + if (opened) + return false; + + m_camera = QJNIObjectPrivate::callStaticObjectMethod("android/hardware/Camera", + "open", + "(I)Landroid/hardware/Camera;", + cameraId); + if (exceptionCheckAndClear(env) || !m_camera.isValid()) + return false; + + m_cameraListener = QJNIObjectPrivate(QtCameraListenerClassName, "(I)V", m_cameraId); + m_info = QJNIObjectPrivate("android/hardware/Camera$CameraInfo"); + m_camera.callStaticMethod<void>("android/hardware/Camera", + "getCameraInfo", + "(ILandroid/hardware/Camera$CameraInfo;)V", + cameraId, + m_info.object()); + + QJNIObjectPrivate params = m_camera.callObjectMethod("getParameters", + "()Landroid/hardware/Camera$Parameters;"); + m_parameters = QJNIObjectPrivate(params); + s_activeCameras |= 1 << cameraId; + + return true; +} + +void AndroidCameraPrivate::release() +{ + m_previewSize = QSize(); + m_parametersMutex.lock(); + m_parameters = QJNIObjectPrivate(); + m_parametersMutex.unlock(); + if (m_camera.isValid()) { + m_camera.callMethod<void>("release"); + s_activeCameras &= ~(1 << m_cameraId); + } +} + +bool AndroidCameraPrivate::lock() +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("lock"); + return !exceptionCheckAndClear(env); +} + +bool AndroidCameraPrivate::unlock() +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("unlock"); + return !exceptionCheckAndClear(env); +} + +bool AndroidCameraPrivate::reconnect() +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("reconnect"); + return !exceptionCheckAndClear(env); +} + +AndroidCamera::CameraFacing AndroidCameraPrivate::getFacing() +{ + return AndroidCamera::CameraFacing(m_info.getField<jint>("facing")); +} + +int AndroidCameraPrivate::getNativeOrientation() +{ + return m_info.getField<jint>("orientation"); +} + +QSize AndroidCameraPrivate::getPreferredPreviewSizeForVideo() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return QSize(); + + QJNIObjectPrivate size = m_parameters.callObjectMethod("getPreferredPreviewSizeForVideo", + "()Landroid/hardware/Camera$Size;"); + + if (!size.isValid()) + return QSize(); + + return QSize(size.getField<jint>("width"), size.getField<jint>("height")); +} + +QList<QSize> AndroidCameraPrivate::getSupportedPreviewSizes() +{ + QList<QSize> list; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (m_parameters.isValid()) { + QJNIObjectPrivate sizeList = m_parameters.callObjectMethod("getSupportedPreviewSizes", + "()Ljava/util/List;"); + int count = sizeList.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate size = sizeList.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + list.append(QSize(size.getField<jint>("width"), size.getField<jint>("height"))); + } + + std::sort(list.begin(), list.end(), qt_sizeLessThan); + } + + return list; +} + +QList<AndroidCamera::FpsRange> AndroidCameraPrivate::getSupportedPreviewFpsRange() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QJNIEnvironmentPrivate env; + + QList<AndroidCamera::FpsRange> rangeList; + + if (m_parameters.isValid()) { + QJNIObjectPrivate rangeListNative = m_parameters.callObjectMethod("getSupportedPreviewFpsRange", + "()Ljava/util/List;"); + int count = rangeListNative.callMethod<jint>("size"); + + rangeList.reserve(count); + + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate range = rangeListNative.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + + jintArray jRange = static_cast<jintArray>(range.object()); + jint* rangeArray = env->GetIntArrayElements(jRange, 0); + + AndroidCamera::FpsRange fpsRange; + + fpsRange.min = rangeArray[0]; + fpsRange.max = rangeArray[1]; + + env->ReleaseIntArrayElements(jRange, rangeArray, 0); + + rangeList << fpsRange; + } + } + + return rangeList; +} + +AndroidCamera::FpsRange AndroidCameraPrivate::getPreviewFpsRange() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QJNIEnvironmentPrivate env; + + AndroidCamera::FpsRange range; + + if (!m_parameters.isValid()) + return range; + + jintArray jRangeArray = env->NewIntArray(2); + m_parameters.callMethod<void>("getPreviewFpsRange", "([I)V", jRangeArray); + + jint* jRangeElements = env->GetIntArrayElements(jRangeArray, 0); + + range.min = jRangeElements[0]; + range.max = jRangeElements[1]; + + env->ReleaseIntArrayElements(jRangeArray, jRangeElements, 0); + env->DeleteLocalRef(jRangeArray); + + return range; +} + +void AndroidCameraPrivate::setPreviewFpsRange(int min, int max) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + QJNIEnvironmentPrivate env; + m_parameters.callMethod<void>("setPreviewFpsRange", "(II)V", min, max); + exceptionCheckAndClear(env); +} + +AndroidCamera::ImageFormat AndroidCameraPrivate::getPreviewFormat() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return AndroidCamera::UnknownImageFormat; + + return AndroidCamera::ImageFormat(m_parameters.callMethod<jint>("getPreviewFormat")); +} + +void AndroidCameraPrivate::setPreviewFormat(AndroidCamera::ImageFormat fmt) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setPreviewFormat", "(I)V", jint(fmt)); + applyParameters(); +} + +QList<AndroidCamera::ImageFormat> AndroidCameraPrivate::getSupportedPreviewFormats() +{ + QList<AndroidCamera::ImageFormat> list; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (m_parameters.isValid()) { + QJNIObjectPrivate formatList = m_parameters.callObjectMethod("getSupportedPreviewFormats", + "()Ljava/util/List;"); + int count = formatList.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate format = formatList.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + list.append(AndroidCamera::ImageFormat(format.callMethod<jint>("intValue"))); + } + } + + return list; +} + +QSize AndroidCameraPrivate::getPreviewSize() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return QSize(); + + QJNIObjectPrivate size = m_parameters.callObjectMethod("getPreviewSize", + "()Landroid/hardware/Camera$Size;"); + + if (!size.isValid()) + return QSize(); + + return QSize(size.getField<jint>("width"), size.getField<jint>("height")); +} + +void AndroidCameraPrivate::updatePreviewSize() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (m_previewSize.isValid()) { + m_parameters.callMethod<void>("setPreviewSize", "(II)V", m_previewSize.width(), m_previewSize.height()); + applyParameters(); + } + + emit previewSizeChanged(); +} + +bool AndroidCameraPrivate::setPreviewTexture(void *surfaceTexture) +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("setPreviewTexture", + "(Landroid/graphics/SurfaceTexture;)V", + static_cast<jobject>(surfaceTexture)); + return !exceptionCheckAndClear(env); +} + +bool AndroidCameraPrivate::setPreviewDisplay(void *surfaceHolder) +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("setPreviewDisplay", + "(Landroid/view/SurfaceHolder;)V", + static_cast<jobject>(surfaceHolder)); + return !exceptionCheckAndClear(env); +} + +void AndroidCameraPrivate::setDisplayOrientation(int degrees) +{ + m_camera.callMethod<void>("setDisplayOrientation", "(I)V", degrees); +} + +bool AndroidCameraPrivate::isZoomSupported() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return false; + + return m_parameters.callMethod<jboolean>("isZoomSupported"); +} + +int AndroidCameraPrivate::getMaxZoom() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getMaxZoom"); +} + +QList<int> AndroidCameraPrivate::getZoomRatios() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QList<int> ratios; + + if (m_parameters.isValid()) { + QJNIObjectPrivate ratioList = m_parameters.callObjectMethod("getZoomRatios", + "()Ljava/util/List;"); + int count = ratioList.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate zoomRatio = ratioList.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + + ratios.append(zoomRatio.callMethod<jint>("intValue")); + } + } + + return ratios; +} + +int AndroidCameraPrivate::getZoom() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getZoom"); +} + +void AndroidCameraPrivate::setZoom(int value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setZoom", "(I)V", value); + applyParameters(); +} + +QString AndroidCameraPrivate::getFlashMode() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QString value; + + if (m_parameters.isValid()) { + QJNIObjectPrivate flashMode = m_parameters.callObjectMethod("getFlashMode", + "()Ljava/lang/String;"); + if (flashMode.isValid()) + value = flashMode.toString(); + } + + return value; +} + +void AndroidCameraPrivate::setFlashMode(const QString &value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setFlashMode", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(value).object()); + applyParameters(); +} + +QString AndroidCameraPrivate::getFocusMode() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QString value; + + if (m_parameters.isValid()) { + QJNIObjectPrivate focusMode = m_parameters.callObjectMethod("getFocusMode", + "()Ljava/lang/String;"); + if (focusMode.isValid()) + value = focusMode.toString(); + } + + return value; +} + +void AndroidCameraPrivate::setFocusMode(const QString &value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setFocusMode", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(value).object()); + applyParameters(); +} + +int AndroidCameraPrivate::getMaxNumFocusAreas() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return 0; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getMaxNumFocusAreas"); +} + +QList<QRect> AndroidCameraPrivate::getFocusAreas() +{ + QList<QRect> areas; + + if (QtAndroidPrivate::androidSdkVersion() < 14) + return areas; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (m_parameters.isValid()) { + QJNIObjectPrivate list = m_parameters.callObjectMethod("getFocusAreas", + "()Ljava/util/List;"); + + if (list.isValid()) { + int count = list.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate area = list.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + + areas.append(areaToRect(area.object())); + } + } + } + + return areas; +} + +void AndroidCameraPrivate::setFocusAreas(const QList<QRect> &areas) +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + QJNIObjectPrivate list; + + if (!areas.isEmpty()) { + QJNIEnvironmentPrivate env; + QJNIObjectPrivate arrayList("java/util/ArrayList", "(I)V", areas.size()); + for (int i = 0; i < areas.size(); ++i) { + arrayList.callMethod<jboolean>("add", + "(Ljava/lang/Object;)Z", + rectToArea(areas.at(i)).object()); + exceptionCheckAndClear(env); + } + list = arrayList; + } + + m_parameters.callMethod<void>("setFocusAreas", "(Ljava/util/List;)V", list.object()); + + applyParameters(); +} + +void AndroidCameraPrivate::autoFocus() +{ + QJNIEnvironmentPrivate env; + + m_camera.callMethod<void>("autoFocus", + "(Landroid/hardware/Camera$AutoFocusCallback;)V", + m_cameraListener.object()); + + if (!exceptionCheckAndClear(env)) + emit autoFocusStarted(); +} + +void AndroidCameraPrivate::cancelAutoFocus() +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("cancelAutoFocus"); + exceptionCheckAndClear(env); +} + +bool AndroidCameraPrivate::isAutoExposureLockSupported() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return false; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return false; + + return m_parameters.callMethod<jboolean>("isAutoExposureLockSupported"); +} + +bool AndroidCameraPrivate::getAutoExposureLock() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return false; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return false; + + return m_parameters.callMethod<jboolean>("getAutoExposureLock"); +} + +void AndroidCameraPrivate::setAutoExposureLock(bool toggle) +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setAutoExposureLock", "(Z)V", toggle); + applyParameters(); +} + +bool AndroidCameraPrivate::isAutoWhiteBalanceLockSupported() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return false; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return false; + + return m_parameters.callMethod<jboolean>("isAutoWhiteBalanceLockSupported"); +} + +bool AndroidCameraPrivate::getAutoWhiteBalanceLock() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return false; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return false; + + return m_parameters.callMethod<jboolean>("getAutoWhiteBalanceLock"); +} + +void AndroidCameraPrivate::setAutoWhiteBalanceLock(bool toggle) +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return; + + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setAutoWhiteBalanceLock", "(Z)V", toggle); + applyParameters(); +} + +int AndroidCameraPrivate::getExposureCompensation() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getExposureCompensation"); +} + +void AndroidCameraPrivate::setExposureCompensation(int value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setExposureCompensation", "(I)V", value); + applyParameters(); +} + +float AndroidCameraPrivate::getExposureCompensationStep() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jfloat>("getExposureCompensationStep"); +} + +int AndroidCameraPrivate::getMinExposureCompensation() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getMinExposureCompensation"); +} + +int AndroidCameraPrivate::getMaxExposureCompensation() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return 0; + + return m_parameters.callMethod<jint>("getMaxExposureCompensation"); +} + +QString AndroidCameraPrivate::getSceneMode() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QString value; + + if (m_parameters.isValid()) { + QJNIObjectPrivate sceneMode = m_parameters.callObjectMethod("getSceneMode", + "()Ljava/lang/String;"); + if (sceneMode.isValid()) + value = sceneMode.toString(); + } + + return value; +} + +void AndroidCameraPrivate::setSceneMode(const QString &value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setSceneMode", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(value).object()); + applyParameters(); +} + +QString AndroidCameraPrivate::getWhiteBalance() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QString value; + + if (m_parameters.isValid()) { + QJNIObjectPrivate wb = m_parameters.callObjectMethod("getWhiteBalance", + "()Ljava/lang/String;"); + if (wb.isValid()) + value = wb.toString(); + } + + return value; +} + +void AndroidCameraPrivate::setWhiteBalance(const QString &value) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setWhiteBalance", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(value).object()); + applyParameters(); + + emit whiteBalanceChanged(); +} + +void AndroidCameraPrivate::updateRotation() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + m_parameters.callMethod<void>("setRotation", "(I)V", m_rotation); + applyParameters(); +} + +QList<QSize> AndroidCameraPrivate::getSupportedPictureSizes() +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QList<QSize> list; + + if (m_parameters.isValid()) { + QJNIObjectPrivate sizeList = m_parameters.callObjectMethod("getSupportedPictureSizes", + "()Ljava/util/List;"); + int count = sizeList.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate size = sizeList.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + list.append(QSize(size.getField<jint>("width"), size.getField<jint>("height"))); + } + + std::sort(list.begin(), list.end(), qt_sizeLessThan); + } + + return list; +} + +void AndroidCameraPrivate::setPictureSize(const QSize &size) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setPictureSize", "(II)V", size.width(), size.height()); + applyParameters(); +} + +void AndroidCameraPrivate::setJpegQuality(int quality) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + if (!m_parameters.isValid()) + return; + + m_parameters.callMethod<void>("setJpegQuality", "(I)V", quality); + applyParameters(); +} + +void AndroidCameraPrivate::startPreview() +{ + QJNIEnvironmentPrivate env; + + setupPreviewFrameCallback(); + m_camera.callMethod<void>("startPreview"); + + if (exceptionCheckAndClear(env)) + emit previewFailedToStart(); + else + emit previewStarted(); +} + +void AndroidCameraPrivate::stopPreview() +{ + QJNIEnvironmentPrivate env; + + // cancel any pending new frame notification + m_cameraListener.callMethod<void>("notifyWhenFrameAvailable", "(Z)V", false); + + m_camera.callMethod<void>("stopPreview"); + + exceptionCheckAndClear(env); + emit previewStopped(); +} + +void AndroidCameraPrivate::takePicture() +{ + QJNIEnvironmentPrivate env; + + // We must clear the preview callback before calling takePicture(), otherwise the call will + // block and the camera server will be frozen until the next device restart... + // That problem only happens on some devices and on the emulator + m_cameraListener.callMethod<void>("clearPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object()); + + m_camera.callMethod<void>("takePicture", "(Landroid/hardware/Camera$ShutterCallback;" + "Landroid/hardware/Camera$PictureCallback;" + "Landroid/hardware/Camera$PictureCallback;)V", + m_cameraListener.object(), + jobject(0), + m_cameraListener.object()); + + if (exceptionCheckAndClear(env)) + emit takePictureFailed(); +} + +void AndroidCameraPrivate::setupPreviewFrameCallback() +{ + m_cameraListener.callMethod<void>("setupPreviewCallback", "(Landroid/hardware/Camera;)V", m_camera.object()); +} + +void AndroidCameraPrivate::notifyNewFrames(bool notify) +{ + m_cameraListener.callMethod<void>("notifyNewFrames", "(Z)V", notify); +} + +void AndroidCameraPrivate::fetchLastPreviewFrame() +{ + QJNIEnvironmentPrivate env; + QJNIObjectPrivate data = m_cameraListener.callObjectMethod("lastPreviewBuffer", "()[B"); + + if (!data.isValid()) { + // If there's no buffer received yet, retry when the next one arrives + m_cameraListener.callMethod<void>("notifyWhenFrameAvailable", "(Z)V", true); + return; + } + + const int arrayLength = env->GetArrayLength(static_cast<jbyteArray>(data.object())); + if (arrayLength == 0) + return; + + QByteArray bytes(arrayLength, Qt::Uninitialized); + env->GetByteArrayRegion(static_cast<jbyteArray>(data.object()), + 0, + arrayLength, + reinterpret_cast<jbyte *>(bytes.data())); + + const int width = m_cameraListener.callMethod<jint>("previewWidth"); + const int height = m_cameraListener.callMethod<jint>("previewHeight"); + const int format = m_cameraListener.callMethod<jint>("previewFormat"); + const int bpl = m_cameraListener.callMethod<jint>("previewBytesPerLine"); + + QVideoFrame frame(new QMemoryVideoBuffer(bytes, bpl), + QSize(width, height), + qt_pixelFormatFromAndroidImageFormat(AndroidCamera::ImageFormat(format))); + + emit lastPreviewFrameFetched(frame); +} + +void AndroidCameraPrivate::applyParameters() +{ + QJNIEnvironmentPrivate env; + m_camera.callMethod<void>("setParameters", + "(Landroid/hardware/Camera$Parameters;)V", + m_parameters.object()); + exceptionCheckAndClear(env); +} + +QStringList AndroidCameraPrivate::callParametersStringListMethod(const QByteArray &methodName) +{ + const std::lock_guard<QRecursiveMutex> locker(m_parametersMutex); + + QStringList stringList; + + if (m_parameters.isValid()) { + QJNIObjectPrivate list = m_parameters.callObjectMethod(methodName.constData(), + "()Ljava/util/List;"); + + if (list.isValid()) { + int count = list.callMethod<jint>("size"); + for (int i = 0; i < count; ++i) { + QJNIObjectPrivate string = list.callObjectMethod("get", + "(I)Ljava/lang/Object;", + i); + stringList.append(string.toString()); + } + } + } + + return stringList; +} + +bool AndroidCamera::initJNI(JNIEnv *env) +{ + jclass clazz = QJNIEnvironmentPrivate::findClass(QtCameraListenerClassName, + env); + + static const JNINativeMethod methods[] = { + {"notifyAutoFocusComplete", "(IZ)V", (void *)notifyAutoFocusComplete}, + {"notifyPictureExposed", "(I)V", (void *)notifyPictureExposed}, + {"notifyPictureCaptured", "(I[B)V", (void *)notifyPictureCaptured}, + {"notifyNewPreviewFrame", "(I[BIIII)V", (void *)notifyNewPreviewFrame}, + {"notifyFrameAvailable", "(I)V", (void *)notifyFrameAvailable} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +QT_END_NAMESPACE + +#include "androidcamera.moc" diff --git a/src/multimedia/platform/android/wrappers/jni/androidcamera_p.h b/src/multimedia/platform/android/wrappers/jni/androidcamera_p.h new file mode 100644 index 000000000..5536e5919 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidcamera_p.h @@ -0,0 +1,248 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2016 Ruslan Baratov +** 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 ANDROIDCAMERA_H +#define ANDROIDCAMERA_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 <QtCore/private/qjni_p.h> +#include <qsize.h> +#include <qrect.h> +#include <QtMultimedia/qcamera.h> + +QT_BEGIN_NAMESPACE + +class QThread; + +class AndroidCameraPrivate; +class AndroidSurfaceTexture; +class AndroidSurfaceHolder; + +struct AndroidCameraInfo +{ + QByteArray name; + QString description; + QCamera::Position position; + int orientation; +}; +Q_DECLARE_TYPEINFO(AndroidCameraInfo, Q_MOVABLE_TYPE); + +class AndroidCamera : public QObject +{ + Q_OBJECT + Q_ENUMS(CameraFacing) + Q_ENUMS(ImageFormat) +public: + enum CameraFacing { + CameraFacingBack = 0, + CameraFacingFront = 1 + }; + + enum ImageFormat { // same values as in android.graphics.ImageFormat Java class + UnknownImageFormat = 0, + RGB565 = 4, + NV16 = 16, + NV21 = 17, + YUY2 = 20, + JPEG = 256, + YV12 = 842094169 + }; + + // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#getSupportedPreviewFpsRange%28%29 + // "The values are multiplied by 1000 and represented in integers" + struct FpsRange { + int min; + int max; + + FpsRange(): min(0), max(0) {} + + qreal getMinReal() const { return min / 1000.0; } + qreal getMaxReal() const { return max / 1000.0; } + + static FpsRange makeFromQReal(qreal min, qreal max) + { + FpsRange range; + range.min = static_cast<int>(min * 1000.0); + range.max = static_cast<int>(max * 1000.0); + return range; + } + }; + + ~AndroidCamera(); + + static AndroidCamera *open(int cameraId); + + int cameraId() const; + + bool lock(); + bool unlock(); + bool reconnect(); + void release(); + + CameraFacing getFacing(); + int getNativeOrientation(); + + QSize getPreferredPreviewSizeForVideo(); + QList<QSize> getSupportedPreviewSizes(); + + QList<FpsRange> getSupportedPreviewFpsRange(); + + FpsRange getPreviewFpsRange(); + void setPreviewFpsRange(FpsRange); + + ImageFormat getPreviewFormat(); + void setPreviewFormat(ImageFormat fmt); + QList<ImageFormat> getSupportedPreviewFormats(); + + QSize previewSize() const; + QSize actualPreviewSize(); + void setPreviewSize(const QSize &size); + bool setPreviewTexture(AndroidSurfaceTexture *surfaceTexture); + bool setPreviewDisplay(AndroidSurfaceHolder *surfaceHolder); + void setDisplayOrientation(int degrees); + + bool isZoomSupported(); + int getMaxZoom(); + QList<int> getZoomRatios(); + int getZoom(); + void setZoom(int value); + + QStringList getSupportedFlashModes(); + QString getFlashMode(); + void setFlashMode(const QString &value); + + QStringList getSupportedFocusModes(); + QString getFocusMode(); + void setFocusMode(const QString &value); + + int getMaxNumFocusAreas(); + QList<QRect> getFocusAreas(); + void setFocusAreas(const QList<QRect> &areas); + + void autoFocus(); + void cancelAutoFocus(); + + bool isAutoExposureLockSupported(); + bool getAutoExposureLock(); + void setAutoExposureLock(bool toggle); + + bool isAutoWhiteBalanceLockSupported(); + bool getAutoWhiteBalanceLock(); + void setAutoWhiteBalanceLock(bool toggle); + + int getExposureCompensation(); + void setExposureCompensation(int value); + float getExposureCompensationStep(); + int getMinExposureCompensation(); + int getMaxExposureCompensation(); + + QStringList getSupportedSceneModes(); + QString getSceneMode(); + void setSceneMode(const QString &value); + + QStringList getSupportedWhiteBalance(); + QString getWhiteBalance(); + void setWhiteBalance(const QString &value); + + void setRotation(int rotation); + int getRotation() const; + + QList<QSize> getSupportedPictureSizes(); + void setPictureSize(const QSize &size); + void setJpegQuality(int quality); + + void startPreview(); + void stopPreview(); + void stopPreviewSynchronous(); + + void takePicture(); + + void setupPreviewFrameCallback(); + void notifyNewFrames(bool notify); + void fetchLastPreviewFrame(); + QJNIObjectPrivate getCameraObject(); + + static int getNumberOfCameras(); + static void getCameraInfo(int id, AndroidCameraInfo *info); + static bool requestCameraPermission(); + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void previewSizeChanged(); + void previewStarted(); + void previewFailedToStart(); + void previewStopped(); + + void autoFocusStarted(); + void autoFocusComplete(bool success); + + void whiteBalanceChanged(); + + void takePictureFailed(); + void pictureExposed(); + void pictureCaptured(const QByteArray &data); + void lastPreviewFrameFetched(const QVideoFrame &frame); + void newPreviewFrame(const QVideoFrame &frame); + +private: + AndroidCamera(AndroidCameraPrivate *d, QThread *worker); + + Q_DECLARE_PRIVATE(AndroidCamera) + AndroidCameraPrivate *d_ptr; + QScopedPointer<QThread> m_worker; +}; + +Q_DECLARE_METATYPE(AndroidCamera::ImageFormat) + +QT_END_NAMESPACE + +#endif // ANDROIDCAMERA_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever.cpp new file mode 100644 index 000000000..3fa480acb --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever.cpp @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** 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 "androidmediametadataretriever_p.h" + +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/private/qjni_p.h> +#include <QtCore/QUrl> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +static bool exceptionCheckAndClear(JNIEnv *env) +{ + if (Q_UNLIKELY(env->ExceptionCheck())) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + return true; + } + + return false; +} + +AndroidMediaMetadataRetriever::AndroidMediaMetadataRetriever() +{ + m_metadataRetriever = QJNIObjectPrivate("android/media/MediaMetadataRetriever"); +} + +AndroidMediaMetadataRetriever::~AndroidMediaMetadataRetriever() +{ + release(); +} + +QString AndroidMediaMetadataRetriever::extractMetadata(MetadataKey key) +{ + QString value; + + QJNIObjectPrivate metadata = m_metadataRetriever.callObjectMethod("extractMetadata", + "(I)Ljava/lang/String;", + jint(key)); + if (metadata.isValid()) + value = metadata.toString(); + + return value; +} + +void AndroidMediaMetadataRetriever::release() +{ + if (!m_metadataRetriever.isValid()) + return; + + m_metadataRetriever.callMethod<void>("release"); +} + +bool AndroidMediaMetadataRetriever::setDataSource(const QUrl &url) +{ + if (!m_metadataRetriever.isValid()) + return false; + + QJNIEnvironmentPrivate env; + + if (url.isLocalFile()) { // also includes qrc files (copied to a temp file by QMediaPlayer) + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.path()); + QJNIObjectPrivate fileInputStream("java/io/FileInputStream", + "(Ljava/lang/String;)V", + string.object()); + + if (exceptionCheckAndClear(env)) + return false; + + QJNIObjectPrivate fd = fileInputStream.callObjectMethod("getFD", + "()Ljava/io/FileDescriptor;"); + if (exceptionCheckAndClear(env)) { + fileInputStream.callMethod<void>("close"); + exceptionCheckAndClear(env); + return false; + } + + m_metadataRetriever.callMethod<void>("setDataSource", + "(Ljava/io/FileDescriptor;)V", + fd.object()); + + bool ok = !exceptionCheckAndClear(env); + + fileInputStream.callMethod<void>("close"); + exceptionCheckAndClear(env); + + if (!ok) + return false; + } else if (url.scheme() == QLatin1String("assets")) { + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.path().mid(1)); // remove first '/' + QJNIObjectPrivate activity(QtAndroidPrivate::activity()); + QJNIObjectPrivate assetManager = activity.callObjectMethod("getAssets", + "()Landroid/content/res/AssetManager;"); + QJNIObjectPrivate assetFd = assetManager.callObjectMethod("openFd", + "(Ljava/lang/String;)Landroid/content/res/AssetFileDescriptor;", + string.object()); + if (exceptionCheckAndClear(env)) + return false; + + QJNIObjectPrivate fd = assetFd.callObjectMethod("getFileDescriptor", + "()Ljava/io/FileDescriptor;"); + if (exceptionCheckAndClear(env)) { + assetFd.callMethod<void>("close"); + exceptionCheckAndClear(env); + return false; + } + + m_metadataRetriever.callMethod<void>("setDataSource", + "(Ljava/io/FileDescriptor;JJ)V", + fd.object(), + assetFd.callMethod<jlong>("getStartOffset"), + assetFd.callMethod<jlong>("getLength")); + + bool ok = !exceptionCheckAndClear(env); + + assetFd.callMethod<void>("close"); + exceptionCheckAndClear(env); + + if (!ok) + return false; + } else if (QtAndroidPrivate::androidSdkVersion() >= 14) { + // On API levels >= 14, only setDataSource(String, Map<String, String>) accepts remote media + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.toString(QUrl::FullyEncoded)); + QJNIObjectPrivate hash("java/util/HashMap"); + + m_metadataRetriever.callMethod<void>("setDataSource", + "(Ljava/lang/String;Ljava/util/Map;)V", + string.object(), + hash.object()); + if (exceptionCheckAndClear(env)) + return false; + } else { + // While on API levels < 14, only setDataSource(Context, Uri) is available and works for + // remote media... + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.toString(QUrl::FullyEncoded)); + QJNIObjectPrivate uri = m_metadataRetriever.callStaticObjectMethod("android/net/Uri", + "parse", + "(Ljava/lang/String;)Landroid/net/Uri;", + string.object()); + if (exceptionCheckAndClear(env)) + return false; + + m_metadataRetriever.callMethod<void>("setDataSource", + "(Landroid/content/Context;Landroid/net/Uri;)V", + QtAndroidPrivate::activity(), + uri.object()); + if (exceptionCheckAndClear(env)) + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever_p.h b/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever_p.h new file mode 100644 index 000000000..8a7b75480 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediametadataretriever_p.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** 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 ANDROIDMEDIAMETADATARETRIEVER_H +#define ANDROIDMEDIAMETADATARETRIEVER_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 <QtCore/private/qjni_p.h> + +QT_BEGIN_NAMESPACE + +class AndroidMediaMetadataRetriever +{ +public: + enum MetadataKey { + Album = 1, + AlbumArtist = 13, + Artist = 2, + Author = 3, + Bitrate = 20, + CDTrackNumber = 0, + Compilation = 15, + Composer = 4, + Date = 5, + DiscNumber = 14, + Duration = 9, + Genre = 6, + HasAudio = 16, + HasVideo = 17, + Location = 23, + MimeType = 12, + NumTracks = 10, + Title = 7, + VideoHeight = 19, + VideoWidth = 18, + VideoRotation = 24, + Writer = 11, + Year = 8 + }; + + AndroidMediaMetadataRetriever(); + ~AndroidMediaMetadataRetriever(); + + QString extractMetadata(MetadataKey key); + bool setDataSource(const QUrl &url); + +private: + void release(); + QJNIObjectPrivate m_metadataRetriever; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDMEDIAMETADATARETRIEVER_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp new file mode 100644 index 000000000..7fc15e788 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer.cpp @@ -0,0 +1,435 @@ +/**************************************************************************** +** +** 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 "androidmediaplayer_p.h" + +#include <QString> +#include <QtCore/private/qjni_p.h> +#include <QtCore/private/qjnihelpers_p.h> +#include "androidsurfacetexture.h" +#include <QList> +#include <QReadWriteLock> + +static const char QtAndroidMediaPlayerClassName[] = "org/qtproject/qt/android/multimedia/QtAndroidMediaPlayer"; +typedef QList<AndroidMediaPlayer *> MediaPlayerList; +Q_GLOBAL_STATIC(MediaPlayerList, mediaPlayers) +Q_GLOBAL_STATIC(QReadWriteLock, rwLock) + +QT_BEGIN_NAMESPACE + +AndroidMediaPlayer::AndroidMediaPlayer() + : QObject() +{ + QWriteLocker locker(rwLock); + auto context = QtAndroidPrivate::activity() ? QtAndroidPrivate::activity() : QtAndroidPrivate::service(); + const jlong id = reinterpret_cast<jlong>(this); + mMediaPlayer = QJNIObjectPrivate(QtAndroidMediaPlayerClassName, + "(Landroid/content/Context;J)V", + context, + id); + mediaPlayers->append(this); +} + +AndroidMediaPlayer::~AndroidMediaPlayer() +{ + QWriteLocker locker(rwLock); + const int i = mediaPlayers->indexOf(this); + Q_ASSERT(i != -1); + mediaPlayers->remove(i); +} + +void AndroidMediaPlayer::release() +{ + mMediaPlayer.callMethod<void>("release"); +} + +void AndroidMediaPlayer::reset() +{ + mMediaPlayer.callMethod<void>("reset"); +} + +int AndroidMediaPlayer::getCurrentPosition() +{ + return mMediaPlayer.callMethod<jint>("getCurrentPosition"); +} + +int AndroidMediaPlayer::getDuration() +{ + return mMediaPlayer.callMethod<jint>("getDuration"); +} + +bool AndroidMediaPlayer::isPlaying() +{ + return mMediaPlayer.callMethod<jboolean>("isPlaying"); +} + +int AndroidMediaPlayer::volume() +{ + return mMediaPlayer.callMethod<jint>("getVolume"); +} + +bool AndroidMediaPlayer::isMuted() +{ + return mMediaPlayer.callMethod<jboolean>("isMuted"); +} + +qreal AndroidMediaPlayer::playbackRate() +{ + qreal rate(1.0); + + if (QtAndroidPrivate::androidSdkVersion() < 23) + return rate; + + QJNIObjectPrivate player = mMediaPlayer.callObjectMethod("getMediaPlayerHandle", "()Landroid/media/MediaPlayer;"); + if (player.isValid()) { + QJNIObjectPrivate playbackParams = player.callObjectMethod("getPlaybackParams", "()Landroid/media/PlaybackParams;"); + if (playbackParams.isValid()) { + const qreal speed = playbackParams.callMethod<jfloat>("getSpeed", "()F"); + QJNIEnvironmentPrivate env; + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + } else { + rate = speed; + } + } + } + + return rate; +} + +jobject AndroidMediaPlayer::display() +{ + return mMediaPlayer.callObjectMethod("display", "()Landroid/view/SurfaceHolder;").object(); +} + +void AndroidMediaPlayer::play() +{ + mMediaPlayer.callMethod<void>("start"); +} + +void AndroidMediaPlayer::pause() +{ + mMediaPlayer.callMethod<void>("pause"); +} + +void AndroidMediaPlayer::stop() +{ + mMediaPlayer.callMethod<void>("stop"); +} + +void AndroidMediaPlayer::seekTo(qint32 msec) +{ + mMediaPlayer.callMethod<void>("seekTo", "(I)V", jint(msec)); +} + +void AndroidMediaPlayer::setMuted(bool mute) +{ + mMediaPlayer.callMethod<void>("mute", "(Z)V", jboolean(mute)); +} + +void AndroidMediaPlayer::setDataSource(const QNetworkRequest &request) +{ + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(request.url().toString(QUrl::FullyEncoded)); + + mMediaPlayer.callMethod<void>("initHeaders", "()V"); + for (auto &header : request.rawHeaderList()) { + auto value = request.rawHeader(header); + mMediaPlayer.callMethod<void>("setHeader", "(Ljava/lang/String;Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(header).object(), QJNIObjectPrivate::fromString(value).object()); + } + + mMediaPlayer.callMethod<void>("setDataSource", "(Ljava/lang/String;)V", string.object()); +} + +void AndroidMediaPlayer::prepareAsync() +{ + mMediaPlayer.callMethod<void>("prepareAsync"); +} + +void AndroidMediaPlayer::setVolume(int volume) +{ + mMediaPlayer.callMethod<void>("setVolume", "(I)V", jint(volume)); +} + +bool AndroidMediaPlayer::setPlaybackRate(qreal rate) +{ + if (QtAndroidPrivate::androidSdkVersion() < 23) { + qWarning("Setting the playback rate on a media player requires Android 6.0 (API level 23) or later"); + return false; + } + + QJNIEnvironmentPrivate env; + + QJNIObjectPrivate player = mMediaPlayer.callObjectMethod("getMediaPlayerHandle", "()Landroid/media/MediaPlayer;"); + if (player.isValid()) { + QJNIObjectPrivate playbackParams = player.callObjectMethod("getPlaybackParams", "()Landroid/media/PlaybackParams;"); + if (playbackParams.isValid()) { + playbackParams.callObjectMethod("setSpeed", "(F)Landroid/media/PlaybackParams;", jfloat(rate)); + // pitch can only be > 0 + if (!qFuzzyIsNull(rate)) + playbackParams.callObjectMethod("setPitch", "(F)Landroid/media/PlaybackParams;", jfloat(qAbs(rate))); + player.callMethod<void>("setPlaybackParams", "(Landroid/media/PlaybackParams;)V", playbackParams.object()); + if (Q_UNLIKELY(env->ExceptionCheck())) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + qWarning() << "Invalid playback rate" << rate; + return false; + } else { + return true; + } + } + } + + return false; +} + +void AndroidMediaPlayer::setDisplay(AndroidSurfaceTexture *surfaceTexture) +{ + mMediaPlayer.callMethod<void>("setDisplay", + "(Landroid/view/SurfaceHolder;)V", + surfaceTexture ? surfaceTexture->surfaceHolder() : 0); +} + +void AndroidMediaPlayer::setAudioRole(QAudio::Role role) +{ + QString str; + switch (role) { + case QAudio::MusicRole: + str = QLatin1String("CONTENT_TYPE_MUSIC"); + break; + case QAudio::VideoRole: + str = QLatin1String("CONTENT_TYPE_MOVIE"); + break; + case QAudio::VoiceCommunicationRole: + str = QLatin1String("USAGE_VOICE_COMMUNICATION"); + break; + case QAudio::AlarmRole: + str = QLatin1String("USAGE_ALARM"); + break; + case QAudio::NotificationRole: + str = QLatin1String("USAGE_NOTIFICATION"); + break; + case QAudio::RingtoneRole: + str = QLatin1String("USAGE_NOTIFICATION_RINGTONE"); + break; + case QAudio::AccessibilityRole: + str = QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY"); + break; + case QAudio::SonificationRole: + str = QLatin1String("CONTENT_TYPE_SONIFICATION"); + break; + case QAudio::GameRole: + str = QLatin1String("USAGE_GAME"); + break; + default: + break; + } + + setCustomAudioRole(str); +} + +void AndroidMediaPlayer::setCustomAudioRole(const QString &role) +{ + QStringList roles = role.split(",", Qt::SkipEmptyParts); + + int type = 0; // CONTENT_TYPE_UNKNOWN + int usage = 0; // USAGE_UNKNOWN + for (int i = 0; i < qMin(2, roles.size()); ++i) { + auto r = roles[i]; + if (r == QLatin1String("CONTENT_TYPE_MOVIE")) + type = 3; + else if (r == QLatin1String("CONTENT_TYPE_MUSIC")) + type = 2; + else if (r == QLatin1String("CONTENT_TYPE_SONIFICATION")) + type = 4; + else if (r == QLatin1String("CONTENT_TYPE_SPEECH")) + type = 1; + else if (r == QLatin1String("USAGE_ALARM")) + usage = 4; + else if (r == QLatin1String("USAGE_ASSISTANCE_ACCESSIBILITY")) + usage = 11; + else if (r == QLatin1String("USAGE_ASSISTANCE_NAVIGATION_GUIDANCE")) + usage = 12; + else if (r == QLatin1String("USAGE_ASSISTANCE_SONIFICATION")) + usage = 13; + else if (r == QLatin1String("USAGE_ASSISTANT")) + usage = 16; + else if (r == QLatin1String("USAGE_GAME")) + usage = 14; + else if (r == QLatin1String("USAGE_MEDIA")) + usage = 1; + else if (r == QLatin1String("USAGE_NOTIFICATION")) + usage = 5; + else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_DELAYED")) + usage = 9; + else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_INSTANT")) + usage = 8; + else if (r == QLatin1String("USAGE_NOTIFICATION_COMMUNICATION_REQUEST")) + usage = 7; + else if (r == QLatin1String("USAGE_NOTIFICATION_EVENT")) + usage = 10; + else if (r == QLatin1String("USAGE_NOTIFICATION_RINGTONE")) + usage = 6; + else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION")) + usage = 2; + else if (r == QLatin1String("USAGE_VOICE_COMMUNICATION_SIGNALLING")) + usage = 3; + } + + mMediaPlayer.callMethod<void>("setAudioAttributes", "(II)V", jint(type), jint(usage)); +} + +static void onErrorNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->error(what, extra); +} + +static void onBufferingUpdateNative(JNIEnv *env, jobject thiz, jint percent, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->bufferingChanged(percent); +} + +static void onProgressUpdateNative(JNIEnv *env, jobject thiz, jint progress, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->progressChanged(progress); +} + +static void onDurationChangedNative(JNIEnv *env, jobject thiz, jint duration, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->durationChanged(duration); +} + +static void onInfoNative(JNIEnv *env, jobject thiz, jint what, jint extra, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->info(what, extra); +} + +static void onStateChangedNative(JNIEnv *env, jobject thiz, jint state, jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->stateChanged(state); +} + +static void onVideoSizeChangedNative(JNIEnv *env, + jobject thiz, + jint width, + jint height, + jlong id) +{ + Q_UNUSED(env); + Q_UNUSED(thiz); + QReadLocker locker(rwLock); + const int i = mediaPlayers->indexOf(reinterpret_cast<AndroidMediaPlayer *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + Q_EMIT (*mediaPlayers)[i]->videoSizeChanged(width, height); +} + +bool AndroidMediaPlayer::initJNI(JNIEnv *env) +{ + jclass clazz = QJNIEnvironmentPrivate::findClass(QtAndroidMediaPlayerClassName, + env); + + static const JNINativeMethod methods[] = { + {"onErrorNative", "(IIJ)V", reinterpret_cast<void *>(onErrorNative)}, + {"onBufferingUpdateNative", "(IJ)V", reinterpret_cast<void *>(onBufferingUpdateNative)}, + {"onProgressUpdateNative", "(IJ)V", reinterpret_cast<void *>(onProgressUpdateNative)}, + {"onDurationChangedNative", "(IJ)V", reinterpret_cast<void *>(onDurationChangedNative)}, + {"onInfoNative", "(IIJ)V", reinterpret_cast<void *>(onInfoNative)}, + {"onVideoSizeChangedNative", "(IIJ)V", reinterpret_cast<void *>(onVideoSizeChangedNative)}, + {"onStateChangedNative", "(IJ)V", reinterpret_cast<void *>(onStateChangedNative)} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h new file mode 100644 index 000000000..bf7a7002b --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediaplayer_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef ANDROIDMEDIAPLAYER_H +#define ANDROIDMEDIAPLAYER_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> +#include <QNetworkRequest> +#include <QtCore/private/qjni_p.h> +#include <QAudio> + +QT_BEGIN_NAMESPACE + +class AndroidSurfaceTexture; + +class AndroidMediaPlayer : public QObject +{ + Q_OBJECT +public: + AndroidMediaPlayer(); + ~AndroidMediaPlayer(); + + enum MediaError + { + // What + MEDIA_ERROR_UNKNOWN = 1, + MEDIA_ERROR_SERVER_DIED = 100, + MEDIA_ERROR_INVALID_STATE = -38, // Undocumented + // Extra + MEDIA_ERROR_IO = -1004, + MEDIA_ERROR_MALFORMED = -1007, + MEDIA_ERROR_UNSUPPORTED = -1010, + MEDIA_ERROR_TIMED_OUT = -110, + MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200, + MEDIA_ERROR_BAD_THINGS_ARE_GOING_TO_HAPPEN = -2147483648 // Undocumented + }; + + enum MediaInfo + { + MEDIA_INFO_UNKNOWN = 1, + MEDIA_INFO_VIDEO_TRACK_LAGGING = 700, + MEDIA_INFO_VIDEO_RENDERING_START = 3, + MEDIA_INFO_BUFFERING_START = 701, + MEDIA_INFO_BUFFERING_END = 702, + MEDIA_INFO_BAD_INTERLEAVING = 800, + MEDIA_INFO_NOT_SEEKABLE = 801, + MEDIA_INFO_METADATA_UPDATE = 802 + }; + + enum MediaPlayerState + { + Uninitialized = 0x1, /* End */ + Idle = 0x2, + Preparing = 0x4, + Prepared = 0x8, + Initialized = 0x10, + Started = 0x20, + Stopped = 0x40, + Paused = 0x80, + PlaybackCompleted = 0x100, + Error = 0x200 + }; + + void release(); + void reset(); + + int getCurrentPosition(); + int getDuration(); + bool isPlaying(); + int volume(); + bool isMuted(); + qreal playbackRate(); + jobject display(); + + void play(); + void pause(); + void stop(); + void seekTo(qint32 msec); + void setMuted(bool mute); + void setDataSource(const QNetworkRequest &request); + void prepareAsync(); + void setVolume(int volume); + bool setPlaybackRate(qreal rate); + void setDisplay(AndroidSurfaceTexture *surfaceTexture); + void setAudioRole(QAudio::Role role); + void setCustomAudioRole(const QString &role); + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void error(qint32 what, qint32 extra); + void bufferingChanged(qint32 percent); + void durationChanged(qint64 duration); + void progressChanged(qint64 progress); + void stateChanged(qint32 state); + void info(qint32 what, qint32 extra); + void videoSizeChanged(qint32 width, qint32 height); + +private: + QJNIObjectPrivate mMediaPlayer; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDMEDIAPLAYER_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp new file mode 100644 index 000000000..97bbd3b6a --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder.cpp @@ -0,0 +1,405 @@ +/**************************************************************************** +** +** 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 "androidmediarecorder_p.h" + +#include "androidcamera_p.h" +#include "androidsurfacetexture_p.h" +#include "androidsurfaceview_p.h" +#include "qandroidglobal_p.h" +#include "qandroidmultimediautils_p.h" +#include <QtCore/private/qjni_p.h> +#include <qmap.h> + +QT_BEGIN_NAMESPACE + +typedef QMap<QString, QJNIObjectPrivate> CamcorderProfiles; +Q_GLOBAL_STATIC(CamcorderProfiles, g_camcorderProfiles) + +static QString profileKey() +{ + return QStringLiteral("%1-%2"); +} + +bool AndroidCamcorderProfile::hasProfile(jint cameraId, Quality quality) +{ + if (g_camcorderProfiles->contains(profileKey().arg(cameraId).arg(quality))) + return true; + + return QJNIObjectPrivate::callStaticMethod<jboolean>("android/media/CamcorderProfile", + "hasProfile", + "(II)Z", + cameraId, + quality); +} + +AndroidCamcorderProfile AndroidCamcorderProfile::get(jint cameraId, Quality quality) +{ + const QString key = profileKey().arg(cameraId).arg(quality); + QMap<QString, QJNIObjectPrivate>::const_iterator it = g_camcorderProfiles->constFind(key); + + if (it != g_camcorderProfiles->constEnd()) + return AndroidCamcorderProfile(*it); + + QJNIObjectPrivate camProfile = QJNIObjectPrivate::callStaticObjectMethod("android/media/CamcorderProfile", + "get", + "(II)Landroid/media/CamcorderProfile;", + cameraId, + quality); + + return AndroidCamcorderProfile((*g_camcorderProfiles)[key] = camProfile); +} + +int AndroidCamcorderProfile::getValue(AndroidCamcorderProfile::Field field) const +{ + switch (field) { + case audioBitRate: + return m_camcorderProfile.getField<jint>("audioBitRate"); + case audioChannels: + return m_camcorderProfile.getField<jint>("audioChannels"); + case audioCodec: + return m_camcorderProfile.getField<jint>("audioCodec"); + case audioSampleRate: + return m_camcorderProfile.getField<jint>("audioSampleRate"); + case duration: + return m_camcorderProfile.getField<jint>("duration"); + case fileFormat: + return m_camcorderProfile.getField<jint>("fileFormat"); + case quality: + return m_camcorderProfile.getField<jint>("quality"); + case videoBitRate: + return m_camcorderProfile.getField<jint>("videoBitRate"); + case videoCodec: + return m_camcorderProfile.getField<jint>("videoCodec"); + case videoFrameHeight: + return m_camcorderProfile.getField<jint>("videoFrameHeight"); + case videoFrameRate: + return m_camcorderProfile.getField<jint>("videoFrameRate"); + case videoFrameWidth: + return m_camcorderProfile.getField<jint>("videoFrameWidth"); + } + + return 0; +} + +AndroidCamcorderProfile::AndroidCamcorderProfile(const QJNIObjectPrivate &camcorderProfile) +{ + m_camcorderProfile = camcorderProfile; +} + +static const char QtMediaRecorderListenerClassName[] = "org/qtproject/qt/android/multimedia/QtMediaRecorderListener"; +typedef QMap<jlong, AndroidMediaRecorder*> MediaRecorderMap; +Q_GLOBAL_STATIC(MediaRecorderMap, mediaRecorders) + +static void notifyError(JNIEnv* , jobject, jlong id, jint what, jint extra) +{ + AndroidMediaRecorder *obj = mediaRecorders->value(id, 0); + if (obj) + emit obj->error(what, extra); +} + +static void notifyInfo(JNIEnv* , jobject, jlong id, jint what, jint extra) +{ + AndroidMediaRecorder *obj = mediaRecorders->value(id, 0); + if (obj) + emit obj->info(what, extra); +} + +AndroidMediaRecorder::AndroidMediaRecorder() + : QObject() + , m_id(reinterpret_cast<jlong>(this)) +{ + m_mediaRecorder = QJNIObjectPrivate("android/media/MediaRecorder"); + if (m_mediaRecorder.isValid()) { + QJNIObjectPrivate listener(QtMediaRecorderListenerClassName, "(J)V", m_id); + m_mediaRecorder.callMethod<void>("setOnErrorListener", + "(Landroid/media/MediaRecorder$OnErrorListener;)V", + listener.object()); + m_mediaRecorder.callMethod<void>("setOnInfoListener", + "(Landroid/media/MediaRecorder$OnInfoListener;)V", + listener.object()); + mediaRecorders->insert(m_id, this); + } +} + +AndroidMediaRecorder::~AndroidMediaRecorder() +{ + mediaRecorders->remove(m_id); +} + +void AndroidMediaRecorder::release() +{ + m_mediaRecorder.callMethod<void>("release"); +} + +bool AndroidMediaRecorder::prepare() +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("prepare"); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + return false; + } + return true; +} + +void AndroidMediaRecorder::reset() +{ + m_mediaRecorder.callMethod<void>("reset"); +} + +bool AndroidMediaRecorder::start() +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("start"); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + return false; + } + return true; +} + +void AndroidMediaRecorder::stop() +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("stop"); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setAudioChannels(int numChannels) +{ + m_mediaRecorder.callMethod<void>("setAudioChannels", "(I)V", numChannels); +} + +void AndroidMediaRecorder::setAudioEncoder(AudioEncoder encoder) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setAudioEncoder", "(I)V", int(encoder)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setAudioEncodingBitRate(int bitRate) +{ + m_mediaRecorder.callMethod<void>("setAudioEncodingBitRate", "(I)V", bitRate); +} + +void AndroidMediaRecorder::setAudioSamplingRate(int samplingRate) +{ + m_mediaRecorder.callMethod<void>("setAudioSamplingRate", "(I)V", samplingRate); +} + +void AndroidMediaRecorder::setAudioSource(AudioSource source) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setAudioSource", "(I)V", int(source)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setCamera(AndroidCamera *camera) +{ + QJNIObjectPrivate cam = camera->getCameraObject(); + m_mediaRecorder.callMethod<void>("setCamera", "(Landroid/hardware/Camera;)V", cam.object()); +} + +void AndroidMediaRecorder::setVideoEncoder(VideoEncoder encoder) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setVideoEncoder", "(I)V", int(encoder)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setVideoEncodingBitRate(int bitRate) +{ + m_mediaRecorder.callMethod<void>("setVideoEncodingBitRate", "(I)V", bitRate); +} + +void AndroidMediaRecorder::setVideoFrameRate(int rate) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setVideoFrameRate", "(I)V", rate); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setVideoSize(const QSize &size) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setVideoSize", "(II)V", size.width(), size.height()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setVideoSource(VideoSource source) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setVideoSource", "(I)V", int(source)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setOrientationHint(int degrees) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setOrientationHint", "(I)V", degrees); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setOutputFormat(OutputFormat format) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setOutputFormat", "(I)V", int(format)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setOutputFile(const QString &path) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setOutputFile", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(path).object()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setSurfaceTexture(AndroidSurfaceTexture *texture) +{ + QJNIEnvironmentPrivate env; + m_mediaRecorder.callMethod<void>("setPreviewDisplay", + "(Landroid/view/Surface;)V", + texture->surface()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +void AndroidMediaRecorder::setSurfaceHolder(AndroidSurfaceHolder *holder) +{ + QJNIEnvironmentPrivate env; + QJNIObjectPrivate surfaceHolder(holder->surfaceHolder()); + QJNIObjectPrivate surface = surfaceHolder.callObjectMethod("getSurface", + "()Landroid/view/Surface;"); + if (!surface.isValid()) + return; + + m_mediaRecorder.callMethod<void>("setPreviewDisplay", + "(Landroid/view/Surface;)V", + surface.object()); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif + env->ExceptionClear(); + } +} + +bool AndroidMediaRecorder::initJNI(JNIEnv *env) +{ + jclass clazz = QJNIEnvironmentPrivate::findClass(QtMediaRecorderListenerClassName, + env); + + static const JNINativeMethod methods[] = { + {"notifyError", "(JII)V", (void *)notifyError}, + {"notifyInfo", "(JII)V", (void *)notifyInfo} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h new file mode 100644 index 000000000..9cba14f75 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmediarecorder_p.h @@ -0,0 +1,187 @@ +/**************************************************************************** +** +** 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 ANDROIDMEDIARECORDER_H +#define ANDROIDMEDIARECORDER_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 <QtCore/private/qjni_p.h> +#include <qsize.h> + +QT_BEGIN_NAMESPACE + +class AndroidCamera; +class AndroidSurfaceTexture; +class AndroidSurfaceHolder; + +class AndroidCamcorderProfile +{ +public: + enum Quality { // Needs to match CamcorderProfile + QUALITY_LOW, + QUALITY_HIGH, + QUALITY_QCIF, + QUALITY_CIF, + QUALITY_480P, + QUALITY_720P, + QUALITY_1080P, + QUALITY_QVGA + }; + + enum Field { + audioBitRate, + audioChannels, + audioCodec, + audioSampleRate, + duration, + fileFormat, + quality, + videoBitRate, + videoCodec, + videoFrameHeight, + videoFrameRate, + videoFrameWidth + }; + + static bool hasProfile(jint cameraId, Quality quality); + static AndroidCamcorderProfile get(jint cameraId, Quality quality); + int getValue(Field field) const; + +private: + AndroidCamcorderProfile(const QJNIObjectPrivate &camcorderProfile); + QJNIObjectPrivate m_camcorderProfile; +}; + +class AndroidMediaRecorder : public QObject +{ + Q_OBJECT +public: + enum AudioEncoder { + DefaultAudioEncoder = 0, + AMR_NB_Encoder = 1, + AMR_WB_Encoder = 2, + AAC = 3 + }; + + enum AudioSource { + DefaultAudioSource = 0, + Mic = 1, + VoiceUplink = 2, + VoiceDownlink = 3, + VoiceCall = 4, + Camcorder = 5, + VoiceRecognition = 6 + }; + + enum VideoEncoder { + DefaultVideoEncoder = 0, + H263 = 1, + H264 = 2, + MPEG_4_SP = 3 + }; + + enum VideoSource { + DefaultVideoSource = 0, + Camera = 1 + }; + + enum OutputFormat { + DefaultOutputFormat = 0, + THREE_GPP = 1, + MPEG_4 = 2, + AMR_NB_Format = 3, + AMR_WB_Format = 4 + }; + + AndroidMediaRecorder(); + ~AndroidMediaRecorder(); + + void release(); + bool prepare(); + void reset(); + + bool start(); + void stop(); + + void setAudioChannels(int numChannels); + void setAudioEncoder(AudioEncoder encoder); + void setAudioEncodingBitRate(int bitRate); + void setAudioSamplingRate(int samplingRate); + void setAudioSource(AudioSource source); + + void setCamera(AndroidCamera *camera); + void setVideoEncoder(VideoEncoder encoder); + void setVideoEncodingBitRate(int bitRate); + void setVideoFrameRate(int rate); + void setVideoSize(const QSize &size); + void setVideoSource(VideoSource source); + + void setOrientationHint(int degrees); + + void setOutputFormat(OutputFormat format); + void setOutputFile(const QString &path); + + void setSurfaceTexture(AndroidSurfaceTexture *texture); + void setSurfaceHolder(AndroidSurfaceHolder *holder); + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void error(int what, int extra); + void info(int what, int extra); + +private: + jlong m_id; + QJNIObjectPrivate m_mediaRecorder; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDMEDIARECORDER_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidmultimediautils.cpp b/src/multimedia/platform/android/wrappers/jni/androidmultimediautils.cpp new file mode 100644 index 000000000..387cb1721 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmultimediautils.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 "androidmultimediautils_p.h" + +#include <QtCore/private/qjni_p.h> + +QT_BEGIN_NAMESPACE + + +void AndroidMultimediaUtils::enableOrientationListener(bool enable) +{ + QJNIObjectPrivate::callStaticMethod<void>("org/qtproject/qt/android/multimedia/QtMultimediaUtils", + "enableOrientationListener", + "(Z)V", + enable); +} + +int AndroidMultimediaUtils::getDeviceOrientation() +{ + return QJNIObjectPrivate::callStaticMethod<jint>("org/qtproject/qt/android/multimedia/QtMultimediaUtils", + "getDeviceOrientation"); +} + +QString AndroidMultimediaUtils::getDefaultMediaDirectory(MediaType type) +{ + QJNIObjectPrivate path = QJNIObjectPrivate::callStaticObjectMethod("org/qtproject/qt/android/multimedia/QtMultimediaUtils", + "getDefaultMediaDirectory", + "(I)Ljava/lang/String;", + jint(type)); + return path.toString(); +} + +void AndroidMultimediaUtils::registerMediaFile(const QString &file) +{ + QJNIObjectPrivate::callStaticMethod<void>("org/qtproject/qt/android/multimedia/QtMultimediaUtils", + "registerMediaFile", + "(Ljava/lang/String;)V", + QJNIObjectPrivate::fromString(file).object()); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidmultimediautils_p.h b/src/multimedia/platform/android/wrappers/jni/androidmultimediautils_p.h new file mode 100644 index 000000000..cb80c5c1e --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidmultimediautils_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** 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 ANDROIDMULTIMEDIAUTILS_H +#define ANDROIDMULTIMEDIAUTILS_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 <QtCore/private/qjni_p.h> + +QT_BEGIN_NAMESPACE + +class AndroidMultimediaUtils +{ +public: + enum MediaType { + Music = 0, + Movies = 1, + DCIM = 2, + Sounds = 3 + }; + + static void enableOrientationListener(bool enable); + static int getDeviceOrientation(); + static QString getDefaultMediaDirectory(MediaType type); + static void registerMediaFile(const QString &file); +}; + +QT_END_NAMESPACE + +#endif // ANDROIDMULTIMEDIAUTILS_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture.cpp b/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture.cpp new file mode 100644 index 000000000..8f9be7c3b --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture.cpp @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** 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 "androidsurfacetexture_p.h" +#include <QtCore/private/qjni_p.h> +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/qmutex.h> + +QT_BEGIN_NAMESPACE + +static const char QtSurfaceTextureListenerClassName[] = "org/qtproject/qt/android/multimedia/QtSurfaceTextureListener"; +typedef QList<jlong> SurfaceTextures; +Q_GLOBAL_STATIC(SurfaceTextures, g_surfaceTextures); +Q_GLOBAL_STATIC(QMutex, g_textureMutex); + +// native method for QtSurfaceTexture.java +static void notifyFrameAvailable(JNIEnv* , jobject, jlong id) +{ + const QMutexLocker lock(g_textureMutex()); + const int idx = g_surfaceTextures->indexOf(id); + if (idx == -1) + return; + + AndroidSurfaceTexture *obj = reinterpret_cast<AndroidSurfaceTexture *>(g_surfaceTextures->at(idx)); + if (obj) + Q_EMIT obj->frameAvailable(); +} + +AndroidSurfaceTexture::AndroidSurfaceTexture(quint32 texName) + : QObject() +{ + Q_STATIC_ASSERT(sizeof (jlong) >= sizeof (void *)); + // API level 11 or higher is required + if (QtAndroidPrivate::androidSdkVersion() < 11) { + qWarning("Camera preview and video playback require Android 3.0 (API level 11) or later."); + return; + } + + QJNIEnvironmentPrivate env; + m_surfaceTexture = QJNIObjectPrivate("android/graphics/SurfaceTexture", "(I)V", jint(texName)); + if (env->ExceptionCheck()) { +#ifdef QT_DEBUG + env->ExceptionDescribe(); +#endif // QT_DEBUG + env->ExceptionClear(); + } + + if (!m_surfaceTexture.isValid()) + return; + + const QMutexLocker lock(g_textureMutex()); + g_surfaceTextures->append(jlong(this)); + QJNIObjectPrivate listener(QtSurfaceTextureListenerClassName, "(J)V", jlong(this)); + setOnFrameAvailableListener(listener); +} + +AndroidSurfaceTexture::~AndroidSurfaceTexture() +{ + if (QtAndroidPrivate::androidSdkVersion() > 13 && m_surface.isValid()) + m_surface.callMethod<void>("release"); + + if (m_surfaceTexture.isValid()) { + release(); + const QMutexLocker lock(g_textureMutex()); + const int idx = g_surfaceTextures->indexOf(jlong(this)); + if (idx != -1) + g_surfaceTextures->remove(idx); + } +} + +QMatrix4x4 AndroidSurfaceTexture::getTransformMatrix() +{ + QMatrix4x4 matrix; + if (!m_surfaceTexture.isValid()) + return matrix; + + QJNIEnvironmentPrivate env; + + jfloatArray array = env->NewFloatArray(16); + m_surfaceTexture.callMethod<void>("getTransformMatrix", "([F)V", array); + env->GetFloatArrayRegion(array, 0, 16, matrix.data()); + env->DeleteLocalRef(array); + + return matrix; +} + +void AndroidSurfaceTexture::release() +{ + if (QtAndroidPrivate::androidSdkVersion() < 14) + return; + + m_surfaceTexture.callMethod<void>("release"); +} + +void AndroidSurfaceTexture::updateTexImage() +{ + if (!m_surfaceTexture.isValid()) + return; + + m_surfaceTexture.callMethod<void>("updateTexImage"); +} + +jobject AndroidSurfaceTexture::surfaceTexture() +{ + return m_surfaceTexture.object(); +} + +jobject AndroidSurfaceTexture::surface() +{ + if (!m_surface.isValid()) { + m_surface = QJNIObjectPrivate("android/view/Surface", + "(Landroid/graphics/SurfaceTexture;)V", + m_surfaceTexture.object()); + } + + return m_surface.object(); +} + +jobject AndroidSurfaceTexture::surfaceHolder() +{ + if (!m_surfaceHolder.isValid()) { + m_surfaceHolder = QJNIObjectPrivate("org/qtproject/qt/android/multimedia/QtSurfaceTextureHolder", + "(Landroid/view/Surface;)V", + surface()); + } + + return m_surfaceHolder.object(); +} + +void AndroidSurfaceTexture::attachToGLContext(quint32 texName) +{ + if (QtAndroidPrivate::androidSdkVersion() < 16 || !m_surfaceTexture.isValid()) + return; + + m_surfaceTexture.callMethod<void>("attachToGLContext", "(I)V", texName); +} + +void AndroidSurfaceTexture::detachFromGLContext() +{ + if (QtAndroidPrivate::androidSdkVersion() < 16 || !m_surfaceTexture.isValid()) + return; + + m_surfaceTexture.callMethod<void>("detachFromGLContext"); +} + +bool AndroidSurfaceTexture::initJNI(JNIEnv *env) +{ + // SurfaceTexture is available since API 11. + if (QtAndroidPrivate::androidSdkVersion() < 11) + return false; + + jclass clazz = QJNIEnvironmentPrivate::findClass(QtSurfaceTextureListenerClassName, + env); + + static const JNINativeMethod methods[] = { + {"notifyFrameAvailable", "(J)V", (void *)notifyFrameAvailable} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +void AndroidSurfaceTexture::setOnFrameAvailableListener(const QJNIObjectPrivate &listener) +{ + m_surfaceTexture.callMethod<void>("setOnFrameAvailableListener", + "(Landroid/graphics/SurfaceTexture$OnFrameAvailableListener;)V", + listener.object()); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture_p.h b/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture_p.h new file mode 100644 index 000000000..5404bb7a7 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidsurfacetexture_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** 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 ANDROIDSURFACETEXTURE_H +#define ANDROIDSURFACETEXTURE_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 <QtCore/private/qjni_p.h> + +#include <QMatrix4x4> + +QT_BEGIN_NAMESPACE + +class AndroidSurfaceTexture : public QObject +{ + Q_OBJECT +public: + explicit AndroidSurfaceTexture(quint32 texName); + ~AndroidSurfaceTexture(); + + jobject surfaceTexture(); + jobject surface(); + jobject surfaceHolder(); + inline bool isValid() const { return m_surfaceTexture.isValid(); } + + QMatrix4x4 getTransformMatrix(); + void release(); // API level 14 + void updateTexImage(); + + void attachToGLContext(quint32 texName); // API level 16 + void detachFromGLContext(); // API level 16 + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void frameAvailable(); + +private: + void setOnFrameAvailableListener(const QJNIObjectPrivate &listener); + + QJNIObjectPrivate m_surfaceTexture; + QJNIObjectPrivate m_surface; + QJNIObjectPrivate m_surfaceHolder; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDSURFACETEXTURE_H diff --git a/src/multimedia/platform/android/wrappers/jni/androidsurfaceview.cpp b/src/multimedia/platform/android/wrappers/jni/androidsurfaceview.cpp new file mode 100644 index 000000000..cbe17a6f9 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidsurfaceview.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** 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 "androidsurfaceview_p.h" + +#include <QtCore/private/qjnihelpers_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtGui/qwindow.h> + +QT_BEGIN_NAMESPACE + +static const char QtSurfaceHolderCallbackClassName[] = "org/qtproject/qt/android/multimedia/QtSurfaceHolderCallback"; +typedef QList<AndroidSurfaceHolder *> SurfaceHolders; +Q_GLOBAL_STATIC(SurfaceHolders, surfaceHolders) +Q_GLOBAL_STATIC(QMutex, shLock) + +AndroidSurfaceHolder::AndroidSurfaceHolder(QJNIObjectPrivate object) + : m_surfaceHolder(object) + , m_surfaceCreated(false) +{ + if (!m_surfaceHolder.isValid()) + return; + + { + QMutexLocker locker(shLock()); + surfaceHolders->append(this); + } + + QJNIObjectPrivate callback(QtSurfaceHolderCallbackClassName, "(J)V", reinterpret_cast<jlong>(this)); + m_surfaceHolder.callMethod<void>("addCallback", + "(Landroid/view/SurfaceHolder$Callback;)V", + callback.object()); +} + +AndroidSurfaceHolder::~AndroidSurfaceHolder() +{ + QMutexLocker locker(shLock()); + const int i = surfaceHolders->indexOf(this); + if (Q_UNLIKELY(i == -1)) + return; + + surfaceHolders->remove(i); +} + +jobject AndroidSurfaceHolder::surfaceHolder() const +{ + return m_surfaceHolder.object(); +} + +bool AndroidSurfaceHolder::isSurfaceCreated() const +{ + QMutexLocker locker(shLock()); + return m_surfaceCreated; +} + +void AndroidSurfaceHolder::handleSurfaceCreated(JNIEnv*, jobject, jlong id) +{ + QMutexLocker locker(shLock()); + const int i = surfaceHolders->indexOf(reinterpret_cast<AndroidSurfaceHolder *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + (*surfaceHolders)[i]->m_surfaceCreated = true; + Q_EMIT (*surfaceHolders)[i]->surfaceCreated(); +} + +void AndroidSurfaceHolder::handleSurfaceDestroyed(JNIEnv*, jobject, jlong id) +{ + QMutexLocker locker(shLock()); + const int i = surfaceHolders->indexOf(reinterpret_cast<AndroidSurfaceHolder *>(id)); + if (Q_UNLIKELY(i == -1)) + return; + + (*surfaceHolders)[i]->m_surfaceCreated = false; +} + +bool AndroidSurfaceHolder::initJNI(JNIEnv *env) +{ + jclass clazz = QJNIEnvironmentPrivate::findClass(QtSurfaceHolderCallbackClassName, + env); + + static const JNINativeMethod methods[] = { + {"notifySurfaceCreated", "(J)V", (void *)AndroidSurfaceHolder::handleSurfaceCreated}, + {"notifySurfaceDestroyed", "(J)V", (void *)AndroidSurfaceHolder::handleSurfaceDestroyed} + }; + + if (clazz && env->RegisterNatives(clazz, + methods, + sizeof(methods) / sizeof(methods[0])) != JNI_OK) { + return false; + } + + return true; +} + +AndroidSurfaceView::AndroidSurfaceView() + : m_window(0) + , m_surfaceHolder(0) + , m_pendingVisible(-1) +{ + QtAndroidPrivate::runOnAndroidThreadSync([this] { + m_surfaceView = QJNIObjectPrivate("android/view/SurfaceView", + "(Landroid/content/Context;)V", + QtAndroidPrivate::activity()); + }, QJNIEnvironmentPrivate()); + + Q_ASSERT(m_surfaceView.isValid()); + + QJNIObjectPrivate holder = m_surfaceView.callObjectMethod("getHolder", + "()Landroid/view/SurfaceHolder;"); + if (!holder.isValid()) { + m_surfaceView = QJNIObjectPrivate(); + } else { + m_surfaceHolder = new AndroidSurfaceHolder(holder); + connect(m_surfaceHolder, &AndroidSurfaceHolder::surfaceCreated, + this, &AndroidSurfaceView::surfaceCreated); + { // Lock now to avoid a race with handleSurfaceCreated() + QMutexLocker locker(shLock()); + m_window = QWindow::fromWinId(WId(m_surfaceView.object())); + + if (m_pendingVisible != -1) + m_window->setVisible(m_pendingVisible); + if (m_pendingGeometry.isValid()) + m_window->setGeometry(m_pendingGeometry); + } + } +} + +AndroidSurfaceView::~AndroidSurfaceView() +{ + delete m_surfaceHolder; + delete m_window; +} + +AndroidSurfaceHolder *AndroidSurfaceView::holder() const +{ + return m_surfaceHolder; +} + +void AndroidSurfaceView::setVisible(bool v) +{ + if (m_window) + m_window->setVisible(v); + else + m_pendingVisible = int(v); +} + +void AndroidSurfaceView::setGeometry(int x, int y, int width, int height) +{ + if (m_window) + m_window->setGeometry(x, y, width, height); + else + m_pendingGeometry = QRect(x, y, width, height); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/android/wrappers/jni/androidsurfaceview_p.h b/src/multimedia/platform/android/wrappers/jni/androidsurfaceview_p.h new file mode 100644 index 000000000..7d89df09b --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/androidsurfaceview_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** 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 ANDROIDSURFACEVIEW_H +#define ANDROIDSURFACEVIEW_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 <QtCore/private/qjni_p.h> +#include <qrect.h> +#include <QtCore/qrunnable.h> + +QT_BEGIN_NAMESPACE + +class QWindow; + +class AndroidSurfaceHolder : public QObject +{ + Q_OBJECT +public: + ~AndroidSurfaceHolder(); + + jobject surfaceHolder() const; + bool isSurfaceCreated() const; + + static bool initJNI(JNIEnv *env); + +Q_SIGNALS: + void surfaceCreated(); + +private: + AndroidSurfaceHolder(QJNIObjectPrivate object); + + static void handleSurfaceCreated(JNIEnv*, jobject, jlong id); + static void handleSurfaceDestroyed(JNIEnv*, jobject, jlong id); + + QJNIObjectPrivate m_surfaceHolder; + bool m_surfaceCreated; + + friend class AndroidSurfaceView; +}; + +class AndroidSurfaceView : public QObject +{ + Q_OBJECT +public: + AndroidSurfaceView(); + ~AndroidSurfaceView(); + + AndroidSurfaceHolder *holder() const; + + void setVisible(bool v); + void setGeometry(int x, int y, int width, int height); + +Q_SIGNALS: + void surfaceCreated(); + +private: + QJNIObjectPrivate m_surfaceView; + QWindow *m_window; + AndroidSurfaceHolder *m_surfaceHolder; + int m_pendingVisible; + QRect m_pendingGeometry; +}; + +QT_END_NAMESPACE + +#endif // ANDROIDSURFACEVIEW_H diff --git a/src/multimedia/platform/android/wrappers/jni/jni.pri b/src/multimedia/platform/android/wrappers/jni/jni.pri new file mode 100644 index 000000000..14bc78573 --- /dev/null +++ b/src/multimedia/platform/android/wrappers/jni/jni.pri @@ -0,0 +1,21 @@ +QT += core-private + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/androidmediaplayer_p.h \ + $$PWD/androidsurfacetexture_p.h \ + $$PWD/androidmediametadataretriever_p.h \ + $$PWD/androidcamera_p.h \ + $$PWD/androidmultimediautils_p.h \ + $$PWD/androidmediarecorder_p.h \ + $$PWD/androidsurfaceview_p.h + +SOURCES += \ + $$PWD/androidmediaplayer.cpp \ + $$PWD/androidsurfacetexture.cpp \ + $$PWD/androidmediametadataretriever.cpp \ + $$PWD/androidcamera.cpp \ + $$PWD/androidmultimediautils.cpp \ + $$PWD/androidmediarecorder.cpp \ + $$PWD/androidsurfaceview.cpp |