diff options
Diffstat (limited to 'src/multimedia/platform')
374 files changed, 64396 insertions, 121 deletions
diff --git a/src/multimedia/platform/alsa/alsa.json b/src/multimedia/platform/alsa/alsa.json deleted file mode 100644 index c2b22dfec..000000000 --- a/src/multimedia/platform/alsa/alsa.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "Keys": ["alsa"] -} diff --git a/src/multimedia/platform/alsa/alsa.pri b/src/multimedia/platform/alsa/alsa.pri index 354e2c03d..aeec7da8a 100644 --- a/src/multimedia/platform/alsa/alsa.pri +++ b/src/multimedia/platform/alsa/alsa.pri @@ -1,11 +1,11 @@ QMAKE_USE_PRIVATE += alsa -HEADERS += platform/alsa/qalsaaudiodeviceinfo_p.h \ - platform/alsa/qalsaaudioinput_p.h \ - platform/alsa/qalsaaudiooutput_p.h \ - platform/alsa/qalsainterface_p.h +HEADERS += $$PWD/qalsaaudiodeviceinfo_p.h \ + $$PWD/qalsaaudioinput_p.h \ + $$PWD/qalsaaudiooutput_p.h \ + $$PWD/qalsainterface_p.h -SOURCES += platform/alsa/qalsaaudiodeviceinfo.cpp \ - platform/alsa/qalsaaudioinput.cpp \ - platform/alsa/qalsaaudiooutput.cpp \ - platform/alsa/qalsainterface.cpp +SOURCES += $$PWD/qalsaaudiodeviceinfo.cpp \ + $$PWD/qalsaaudioinput.cpp \ + $$PWD/qalsaaudiooutput.cpp \ + $$PWD/qalsainterface.cpp 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 diff --git a/src/multimedia/platform/avfoundation/avfoundation.pri b/src/multimedia/platform/avfoundation/avfoundation.pri new file mode 100644 index 000000000..29be7ec58 --- /dev/null +++ b/src/multimedia/platform/avfoundation/avfoundation.pri @@ -0,0 +1,18 @@ +LIBS += -framework CoreFoundation \ + -framework Foundation \ + -framework AudioToolbox \ + -framework CoreAudio \ + -framework QuartzCore \ + -framework CoreMedia \ + -framework CoreVideo \ + -framework QuartzCore \ + -framework Metal +osx:LIBS += -framework AppKit \ + -framework AudioUnit +ios:LIBS += -framework CoreGraphics \ + -framework CoreVideo + +QMAKE_USE += avfoundation + +include(mediaplayer/mediaplayer.pri) +!tvos:include(camera/camera.pri) diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm new file mode 100644 index 000000000..b613ca32a --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol.mm @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** 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 "avfaudioencodersettingscontrol_p.h" + +#include "avfcameraservice_p.h" +#include "avfcamerasession_p.h" + +#include <AVFoundation/AVFoundation.h> +#include <CoreAudio/CoreAudioTypes.h> + +QT_BEGIN_NAMESPACE + +struct AudioCodecInfo +{ + QString description; + int id; + + AudioCodecInfo() : id(0) { } + AudioCodecInfo(const QString &desc, int i) + : description(desc), id(i) + { } +}; + +typedef QMap<QString, AudioCodecInfo> SupportedAudioCodecs; +Q_GLOBAL_STATIC_WITH_ARGS(QString , defaultCodec, (QLatin1String("aac"))) +Q_GLOBAL_STATIC(SupportedAudioCodecs, supportedCodecs) + +AVFAudioEncoderSettingsControl::AVFAudioEncoderSettingsControl(AVFCameraService *service) + : QAudioEncoderSettingsControl() + , m_service(service) +{ + if (supportedCodecs->isEmpty()) { + supportedCodecs->insert(QStringLiteral("lpcm"), + AudioCodecInfo(QStringLiteral("Linear PCM"), + kAudioFormatLinearPCM)); + supportedCodecs->insert(QStringLiteral("ulaw"), + AudioCodecInfo(QStringLiteral("PCM Mu-Law 2:1"), + kAudioFormatULaw)); + supportedCodecs->insert(QStringLiteral("alaw"), + AudioCodecInfo(QStringLiteral("PCM A-Law 2:1"), + kAudioFormatALaw)); + supportedCodecs->insert(QStringLiteral("ima4"), + AudioCodecInfo(QStringLiteral("IMA 4:1 ADPCM"), + kAudioFormatAppleIMA4)); + supportedCodecs->insert(QStringLiteral("alac"), + AudioCodecInfo(QStringLiteral("Apple Lossless Audio Codec"), + kAudioFormatAppleLossless)); + supportedCodecs->insert(QStringLiteral("aac"), + AudioCodecInfo(QStringLiteral("MPEG-4 Low Complexity AAC"), + kAudioFormatMPEG4AAC)); + supportedCodecs->insert(QStringLiteral("aach"), + AudioCodecInfo(QStringLiteral("MPEG-4 High Efficiency AAC"), + kAudioFormatMPEG4AAC_HE)); + supportedCodecs->insert(QStringLiteral("aacl"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Low Delay"), + kAudioFormatMPEG4AAC_LD)); + supportedCodecs->insert(QStringLiteral("aace"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Enhanced Low Delay"), + kAudioFormatMPEG4AAC_ELD)); + supportedCodecs->insert(QStringLiteral("aacf"), + AudioCodecInfo(QStringLiteral("MPEG-4 AAC Enhanced Low Delay with SBR"), + kAudioFormatMPEG4AAC_ELD_SBR)); + supportedCodecs->insert(QStringLiteral("aacp"), + AudioCodecInfo(QStringLiteral("MPEG-4 HE AAC V2"), + kAudioFormatMPEG4AAC_HE_V2)); + supportedCodecs->insert(QStringLiteral("ilbc"), + AudioCodecInfo(QStringLiteral("iLBC"), + kAudioFormatiLBC)); + } +} + +QStringList AVFAudioEncoderSettingsControl::supportedAudioCodecs() const +{ + return supportedCodecs->keys(); +} + +QString AVFAudioEncoderSettingsControl::codecDescription(const QString &codecName) const +{ + return supportedCodecs->value(codecName).description; +} + +QList<int> AVFAudioEncoderSettingsControl::supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (continuous) + *continuous = true; + + return QList<int>() << 8000 << 96000; +} + +QAudioEncoderSettings AVFAudioEncoderSettingsControl::audioSettings() const +{ + return m_actualSettings; +} + +void AVFAudioEncoderSettingsControl::setAudioSettings(const QAudioEncoderSettings &settings) +{ + if (m_requestedSettings == settings) + return; + + m_requestedSettings = m_actualSettings = settings; +} + +NSDictionary *AVFAudioEncoderSettingsControl::applySettings() +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return nil; + } + + NSMutableDictionary *settings = [NSMutableDictionary dictionary]; + + QString codec = m_requestedSettings.codec().isEmpty() ? *defaultCodec : m_requestedSettings.codec(); + if (!supportedCodecs->contains(codec)) { + qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData()); + codec = *defaultCodec; + } + [settings setObject:[NSNumber numberWithInt:supportedCodecs->value(codec).id] forKey:AVFormatIDKey]; + m_actualSettings.setCodec(codec); + +#ifdef Q_OS_OSX + if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + int quality; + switch (m_requestedSettings.quality()) { + case QMultimedia::VeryLowQuality: + quality = AVAudioQualityMin; + break; + case QMultimedia::LowQuality: + quality = AVAudioQualityLow; + break; + case QMultimedia::HighQuality: + quality = AVAudioQualityHigh; + break; + case QMultimedia::VeryHighQuality: + quality = AVAudioQualityMax; + break; + case QMultimedia::NormalQuality: + default: + quality = AVAudioQualityMedium; + break; + } + [settings setObject:[NSNumber numberWithInt:quality] forKey:AVEncoderAudioQualityKey]; + + } else +#endif + if (m_requestedSettings.bitRate() > 0){ + [settings setObject:[NSNumber numberWithInt:m_requestedSettings.bitRate()] forKey:AVEncoderBitRateKey]; + } + + int sampleRate = m_requestedSettings.sampleRate(); + int channelCount = m_requestedSettings.channelCount(); + +#ifdef Q_OS_IOS + // Some keys are mandatory only on iOS + if (codec == QLatin1String("lpcm")) { + [settings setObject:[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsBigEndianKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsFloatKey]; + [settings setObject:[NSNumber numberWithInt:NO] forKey:AVLinearPCMIsNonInterleaved]; + } + + if (codec == QLatin1String("alac")) + [settings setObject:[NSNumber numberWithInt:24] forKey:AVEncoderBitDepthHintKey]; + + if (sampleRate <= 0) + sampleRate = codec == QLatin1String("ilbc") ? 8000 : 44100; + if (channelCount <= 0) + channelCount = codec == QLatin1String("ilbc") ? 1 : 2; +#endif + + if (sampleRate > 0) { + [settings setObject:[NSNumber numberWithInt:sampleRate] forKey:AVSampleRateKey]; + m_actualSettings.setSampleRate(sampleRate); + } + if (channelCount > 0) { + [settings setObject:[NSNumber numberWithInt:channelCount] forKey:AVNumberOfChannelsKey]; + m_actualSettings.setChannelCount(channelCount); + } + + return settings; +} + +void AVFAudioEncoderSettingsControl::unapplySettings() +{ + m_actualSettings = m_requestedSettings; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_p.h new file mode 100644 index 000000000..b1851a5bf --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfaudioencodersettingscontrol_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 AVFAUDIOENCODERSETTINGSCONTROL_H +#define AVFAUDIOENCODERSETTINGSCONTROL_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.h> + +@class NSDictionary; +@class AVCaptureAudioDataOutput; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFAudioEncoderSettingsControl : public QAudioEncoderSettingsControl +{ +public: + explicit AVFAudioEncoderSettingsControl(AVFCameraService *service); + + QStringList supportedAudioCodecs() const override; + QString codecDescription(const QString &codecName) const override; + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous = nullptr) const override; + QAudioEncoderSettings audioSettings() const override; + void setAudioSettings(const QAudioEncoderSettings &settings) override; + + NSDictionary *applySettings(); + void unapplySettings(); + +private: + AVFCameraService *m_service; + + QAudioEncoderSettings m_requestedSettings; + QAudioEncoderSettings m_actualSettings; +}; + +QT_END_NAMESPACE + +#endif // AVFAUDIOENCODERSETTINGSCONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm new file mode 100644 index 000000000..21ff70917 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol.mm @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfaudioinputselectorcontrol_p.h" +#include "avfcameraservice_p.h" + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +AVFAudioInputSelectorControl::AVFAudioInputSelectorControl(AVFCameraService *service, QObject *parent) + : QAudioInputSelectorControl(parent) + , m_dirty(true) +{ + Q_UNUSED(service); + NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio]; + for (AVCaptureDevice *device in videoDevices) { + QString deviceId = QString::fromUtf8([[device uniqueID] UTF8String]); + m_devices << deviceId; + m_deviceDescriptions.insert(deviceId, + QString::fromUtf8([[device localizedName] UTF8String])); + } + + AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + if (defaultDevice) { + m_defaultDevice = QString::fromUtf8([defaultDevice.uniqueID UTF8String]); + m_activeInput = m_defaultDevice; + } +} + +AVFAudioInputSelectorControl::~AVFAudioInputSelectorControl() +{ +} + +QList<QString> AVFAudioInputSelectorControl::availableInputs() const +{ + return m_devices; +} + +QString AVFAudioInputSelectorControl::inputDescription(const QString &name) const +{ + return m_deviceDescriptions.value(name); +} + +QString AVFAudioInputSelectorControl::defaultInput() const +{ + return m_defaultDevice; +} + +QString AVFAudioInputSelectorControl::activeInput() const +{ + return m_activeInput; +} + +void AVFAudioInputSelectorControl::setActiveInput(const QString &name) +{ + if (name != m_activeInput) { + m_activeInput = name; + m_dirty = true; + + Q_EMIT activeInputChanged(m_activeInput); + } +} + +AVCaptureDevice *AVFAudioInputSelectorControl::createCaptureDevice() +{ + m_dirty = false; + AVCaptureDevice *device = nullptr; + + if (!m_activeInput.isEmpty()) { + device = [AVCaptureDevice deviceWithUniqueID: + [NSString stringWithUTF8String: + m_activeInput.toUtf8().constData()]]; + } + + if (!device) + device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; + + return device; +} + +#include "moc_avfaudioinputselectorcontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h new file mode 100644 index 000000000..90a9bc3fc --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfaudioinputselectorcontrol_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef AVFAUDIOINPUTSELECTORCONTROL_H +#define AVFAUDIOINPUTSELECTORCONTROL_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 <QtMultimedia/qaudioinputselectorcontrol.h> +#include <QtCore/qstringlist.h> + +#import <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; + +class AVFAudioInputSelectorControl : public QAudioInputSelectorControl +{ +Q_OBJECT +public: + AVFAudioInputSelectorControl(AVFCameraService *service, QObject *parent = nullptr); + ~AVFAudioInputSelectorControl(); + + QList<QString> availableInputs() const override; + QString inputDescription(const QString &name) const override; + QString defaultInput() const override; + QString activeInput() const override; + +public Q_SLOTS: + void setActiveInput(const QString &name) override; + +public: + //device changed since the last createCaptureDevice() + bool isDirty() const { return m_dirty; } + AVCaptureDevice *createCaptureDevice(); + +private: + QString m_activeInput; + bool m_dirty; + QString m_defaultDevice; + QStringList m_devices; + QMap<QString, QString> m_deviceDescriptions; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm new file mode 100644 index 000000000..27c28587f --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameracontrol.mm @@ -0,0 +1,530 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfcameracontrol_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcamerautility_p.h" +#include "avfcamerarenderercontrol_p.h" +#include "qabstractvideosurface.h" + +QT_USE_NAMESPACE + +AVFCameraControl::AVFCameraControl(AVFCameraService *service, QObject *parent) + : QCameraControl(parent) + , m_session(service->session()) + , m_service(service) + , m_state(QCamera::UnloadedState) + , m_lastStatus(QCamera::UnloadedStatus) + , m_captureMode(QCamera::CaptureStillImage) +{ + Q_UNUSED(service); + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus())); + connect(this, &AVFCameraControl::captureModeChanged, m_session, &AVFCameraSession::onCaptureModeChanged); +} + +AVFCameraControl::~AVFCameraControl() +{ +} + +QCamera::State AVFCameraControl::state() const +{ + return m_state; +} + +void AVFCameraControl::setState(QCamera::State state) +{ + if (m_state == state) + return; + m_state = state; + m_session->setState(state); + + Q_EMIT stateChanged(m_state); + updateStatus(); +} + +QCamera::Status AVFCameraControl::status() const +{ + static QCamera::Status statusTable[3][3] = { + { QCamera::UnloadedStatus, QCamera::UnloadingStatus, QCamera::StoppingStatus }, //Unloaded state + { QCamera::LoadingStatus, QCamera::LoadedStatus, QCamera::StoppingStatus }, //Loaded state + { QCamera::LoadingStatus, QCamera::StartingStatus, QCamera::ActiveStatus } //ActiveState + }; + + return statusTable[m_state][m_session->state()]; +} + +void AVFCameraControl::updateStatus() +{ + QCamera::Status newStatus = status(); + + if (m_lastStatus != newStatus) { + qDebugCamera() << "Camera status changed: " << m_lastStatus << " -> " << newStatus; + m_lastStatus = newStatus; + Q_EMIT statusChanged(m_lastStatus); + } +} + +QCamera::CaptureModes AVFCameraControl::captureMode() const +{ + return m_captureMode; +} + +void AVFCameraControl::setCaptureMode(QCamera::CaptureModes mode) +{ + if (m_captureMode == mode) + return; + + m_captureMode = mode; + Q_EMIT captureModeChanged(mode); +} + +bool AVFCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + return true; +} + +bool AVFCameraControl::canChangeProperty(QCameraControl::PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(changeType); + Q_UNUSED(status); + + return true; +} + +QCamera::LockTypes AVFCameraControl::supportedLocks() const +{ + return {}; +} + +QCamera::LockStatus AVFCameraControl::lockStatus(QCamera::LockType) const +{ + return QCamera::Unlocked; +} + +void AVFCameraControl::searchAndLock(QCamera::LockTypes locks) +{ + Q_UNUSED(locks); +} + +void AVFCameraControl::unlock(QCamera::LockTypes locks) +{ + Q_UNUSED(locks); +} + + +namespace { + +bool qt_framerates_sane(const QCameraViewfinderSettings &settings) +{ + const qreal minFPS = settings.minimumFrameRate(); + const qreal maxFPS = settings.maximumFrameRate(); + + if (minFPS < 0. || maxFPS < 0.) + return false; + + return !maxFPS || maxFPS >= minFPS; +} + +} + +QList<QCameraViewfinderSettings> AVFCameraControl::supportedViewfinderSettings() const +{ + QList<QCameraViewfinderSettings> supportedSettings; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "no capture device found"; + return supportedSettings; + } + + QVector<AVFPSRange> framerates; + + QVector<QVideoFrame::PixelFormat> pixelFormats(viewfinderPixelFormats()); + + if (!pixelFormats.size()) + pixelFormats << QVideoFrame::Format_Invalid; // The default value. + + if (!captureDevice.formats || !captureDevice.formats.count) { + qDebugCamera() << Q_FUNC_INFO << "no capture device formats found"; + return supportedSettings; + } + + const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice, + m_service->session()->defaultCodec())); + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats[i]; + + const QSize res(qt_device_format_resolution(format)); + if (res.isNull() || !res.isValid()) + continue; + const QSize par(qt_device_format_pixel_aspect_ratio(format)); + if (par.isNull() || !par.isValid()) + continue; + + framerates = qt_device_format_framerates(format); + if (!framerates.size()) + framerates << AVFPSRange(); // The default value. + + for (int i = 0; i < pixelFormats.size(); ++i) { + for (int j = 0; j < framerates.size(); ++j) { + QCameraViewfinderSettings newSet; + newSet.setResolution(res); + newSet.setPixelAspectRatio(par); + newSet.setPixelFormat(pixelFormats[i]); + newSet.setMinimumFrameRate(framerates[j].first); + newSet.setMaximumFrameRate(framerates[j].second); + supportedSettings << newSet; + } + } + } + + return supportedSettings; +} + +QCameraViewfinderSettings AVFCameraControl::viewfinderSettings() const +{ + QCameraViewfinderSettings settings = m_settings; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "no capture device found"; + return settings; + } + + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return settings; + } + + if (!captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; + return settings; + } + + const QSize res(qt_device_format_resolution(captureDevice.activeFormat)); + const QSize par(qt_device_format_pixel_aspect_ratio(captureDevice.activeFormat)); + if (res.isNull() || !res.isValid() || par.isNull() || !par.isValid()) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain resolution/pixel aspect ratio"; + return settings; + } + + settings.setResolution(res); + settings.setPixelAspectRatio(par); + + const AVFPSRange fps = qt_current_framerates(captureDevice, videoConnection()); + settings.setMinimumFrameRate(fps.first); + settings.setMaximumFrameRate(fps.second); + + AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr; + if (videoOutput) { + NSObject *obj = [videoOutput.videoSettings objectForKey:(id)kCVPixelBufferPixelFormatTypeKey]; + if (obj && [obj isKindOfClass:[NSNumber class]]) { + NSNumber *nsNum = static_cast<NSNumber *>(obj); + settings.setPixelFormat(QtPixelFormatFromCVFormat([nsNum unsignedIntValue])); + } + } + + return settings; +} + +void AVFCameraControl::setViewfinderSettings(const QCameraViewfinderSettings &settings) +{ + if (m_settings == settings) + return; + + m_settings = settings; +#if defined(Q_OS_IOS) + bool active = m_service->session()->state() == QCamera::ActiveState; + if (active) + [m_service->session()->captureSession() beginConfiguration]; + applySettings(m_settings); + if (active) + [m_service->session()->captureSession() commitConfiguration]; +#else + applySettings(m_settings); +#endif +} + +QVideoFrame::PixelFormat AVFCameraControl::QtPixelFormatFromCVFormat(unsigned avPixelFormat) +{ + // BGRA <-> ARGB "swap" is intentional: + // to work correctly with GL_RGBA, color swap shaders + // (in QSG node renderer etc.). + switch (avPixelFormat) { + case kCVPixelFormatType_32ARGB: + return QVideoFrame::Format_BGRA32; + case kCVPixelFormatType_32BGRA: + return QVideoFrame::Format_ARGB32; + case kCVPixelFormatType_24RGB: + return QVideoFrame::Format_RGB24; + case kCVPixelFormatType_24BGR: + return QVideoFrame::Format_BGR24; + case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: + case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange: + return QVideoFrame::Format_NV12; + case kCVPixelFormatType_422YpCbCr8: + return QVideoFrame::Format_UYVY; + case kCVPixelFormatType_422YpCbCr8_yuvs: + return QVideoFrame::Format_YUYV; + default: + return QVideoFrame::Format_Invalid; + } +} + +bool AVFCameraControl::CVPixelFormatFromQtFormat(QVideoFrame::PixelFormat qtFormat, unsigned &conv) +{ + // BGRA <-> ARGB "swap" is intentional: + // to work correctly with GL_RGBA, color swap shaders + // (in QSG node renderer etc.). + switch (qtFormat) { + case QVideoFrame::Format_ARGB32: + conv = kCVPixelFormatType_32BGRA; + break; + case QVideoFrame::Format_BGRA32: + conv = kCVPixelFormatType_32ARGB; + break; + case QVideoFrame::Format_NV12: + conv = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange; + break; + case QVideoFrame::Format_UYVY: + conv = kCVPixelFormatType_422YpCbCr8; + break; + case QVideoFrame::Format_YUYV: + conv = kCVPixelFormatType_422YpCbCr8_yuvs; + break; + // These two formats below are not supported + // by QSGVideoNodeFactory_RGB, so for now I have to + // disable them. + /* + case QVideoFrame::Format_RGB24: + conv = kCVPixelFormatType_24RGB; + break; + case QVideoFrame::Format_BGR24: + conv = kCVPixelFormatType_24BGR; + break; + */ + default: + return false; + } + + return true; +} + +AVCaptureDeviceFormat *AVFCameraControl::findBestFormatMatch(const QCameraViewfinderSettings &settings) const +{ + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice || settings.isNull()) + return nil; + + const QSize &resolution = settings.resolution(); + if (!resolution.isNull() && resolution.isValid()) { + // Either the exact match (including high resolution for images on iOS) + // or a format with a resolution close to the requested one. + return qt_find_best_resolution_match(captureDevice, resolution, + m_service->session()->defaultCodec(), false); + } + + // No resolution requested, what about framerates? + if (!qt_framerates_sane(settings)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerate requested (min/max):" + << settings.minimumFrameRate() << settings.maximumFrameRate(); + return nil; + } + + const qreal minFPS(settings.minimumFrameRate()); + const qreal maxFPS(settings.maximumFrameRate()); + if (minFPS || maxFPS) + return qt_find_best_framerate_match(captureDevice, + m_service->session()->defaultCodec(), + maxFPS ? maxFPS : minFPS); + // Ignore PAR for the moment (PAR without resolution can + // pick a format with really bad resolution). + // No need to test pixel format, just return settings. + + return nil; +} + +QVector<QVideoFrame::PixelFormat> AVFCameraControl::viewfinderPixelFormats() const +{ + QVector<QVideoFrame::PixelFormat> qtFormats; + + AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr; + if (!videoOutput) { + qDebugCamera() << Q_FUNC_INFO << "no video output found"; + return qtFormats; + } + + NSArray *pixelFormats = [videoOutput availableVideoCVPixelFormatTypes]; + + for (NSObject *obj in pixelFormats) { + if (![obj isKindOfClass:[NSNumber class]]) + continue; + + NSNumber *formatAsNSNumber = static_cast<NSNumber *>(obj); + // It's actually FourCharCode (== UInt32): + const QVideoFrame::PixelFormat qtFormat(QtPixelFormatFromCVFormat([formatAsNSNumber unsignedIntValue])); + if (qtFormat != QVideoFrame::Format_Invalid + && !qtFormats.contains(qtFormat)) { // Can happen, for example, with 8BiPlanar existing in video/full range. + qtFormats << qtFormat; + } + } + + return qtFormats; +} + +bool AVFCameraControl::convertPixelFormatIfSupported(QVideoFrame::PixelFormat qtFormat, + unsigned &avfFormat)const +{ + AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr; + if (!videoOutput) + return false; + + unsigned conv = 0; + if (!CVPixelFormatFromQtFormat(qtFormat, conv)) + return false; + + NSArray *formats = [videoOutput availableVideoCVPixelFormatTypes]; + if (!formats || !formats.count) + return false; + + if (m_service->videoOutput()->surface()) { + const QAbstractVideoSurface *surface = m_service->videoOutput()->surface(); + QAbstractVideoBuffer::HandleType h = m_service->videoOutput()->supportsTextures() + ? QAbstractVideoBuffer::GLTextureHandle + : QAbstractVideoBuffer::NoHandle; + if (!surface->supportedPixelFormats(h).contains(qtFormat)) + return false; + } + + bool found = false; + for (NSObject *obj in formats) { + if (![obj isKindOfClass:[NSNumber class]]) + continue; + + NSNumber *nsNum = static_cast<NSNumber *>(obj); + if ([nsNum unsignedIntValue] == conv) { + avfFormat = conv; + found = true; + } + } + + return found; +} + +bool AVFCameraControl::applySettings(const QCameraViewfinderSettings &settings) +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return false; + } + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice) + return false; + + bool activeFormatChanged = false; + + AVCaptureDeviceFormat *match = findBestFormatMatch(settings); + if (match) { + activeFormatChanged = qt_set_active_format(captureDevice, match, false); + } else { + qDebugCamera() << Q_FUNC_INFO << "matching device format not found"; + // We still can update the pixel format at least. + } + + AVCaptureVideoDataOutput *videoOutput = m_service->videoOutput() ? m_service->videoOutput()->videoDataOutput() : nullptr; + if (videoOutput) { + unsigned avfPixelFormat = 0; + if (!convertPixelFormatIfSupported(settings.pixelFormat(), avfPixelFormat)) { + // If the the pixel format is not specified or invalid, pick the preferred video surface + // format, or if no surface is set, the preferred capture device format + + const QVector<QVideoFrame::PixelFormat> deviceFormats = viewfinderPixelFormats(); + QAbstractVideoSurface *surface = m_service->videoOutput()->surface(); + QVideoFrame::PixelFormat pickedFormat = deviceFormats.first(); + if (surface) { + pickedFormat = QVideoFrame::Format_Invalid; + QAbstractVideoBuffer::HandleType h = m_service->videoOutput()->supportsTextures() + ? QAbstractVideoBuffer::GLTextureHandle + : QAbstractVideoBuffer::NoHandle; + QList<QVideoFrame::PixelFormat> surfaceFormats = surface->supportedPixelFormats(h); + for (int i = 0; i < surfaceFormats.count(); ++i) { + const QVideoFrame::PixelFormat surfaceFormat = surfaceFormats.at(i); + if (deviceFormats.contains(surfaceFormat)) { + pickedFormat = surfaceFormat; + break; + } + } + } + + CVPixelFormatFromQtFormat(pickedFormat, avfPixelFormat); + } + + NSMutableDictionary *videoSettings = [NSMutableDictionary dictionaryWithCapacity:1]; + [videoSettings setObject:[NSNumber numberWithUnsignedInt:avfPixelFormat] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]; + + const AVFConfigurationLock lock(captureDevice); + if (!lock) + qWarning("Failed to set active format (lock failed)"); + + videoOutput.videoSettings = videoSettings; + } + + qt_set_framerate_limits(captureDevice, videoConnection(), settings.minimumFrameRate(), settings.maximumFrameRate()); + + return activeFormatChanged; +} + +QCameraViewfinderSettings AVFCameraControl::requestedSettings() const +{ + return m_settings; +} + +AVCaptureConnection *AVFCameraControl::videoConnection() const +{ + if (!m_service->videoOutput() || !m_service->videoOutput()->videoDataOutput()) + return nil; + + return [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; +} + +#include "moc_avfcameracontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h new file mode 100644 index 000000000..2cb464fe5 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameracontrol_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 AVFCAMERACONTROL_H +#define AVFCAMERACONTROL_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/qobject.h> + +#include <QtMultimedia/qcameracontrol.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; +@class AVCaptureDeviceFormat; +@class AVCaptureConnection; + +class AVFCameraControl : public QCameraControl +{ +Q_OBJECT +public: + AVFCameraControl(AVFCameraService *service, QObject *parent = nullptr); + ~AVFCameraControl(); + + 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) 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; + + // "Converters": + static QVideoFrame::PixelFormat QtPixelFormatFromCVFormat(unsigned avPixelFormat); + static bool CVPixelFormatFromQtFormat(QVideoFrame::PixelFormat qtFormat, unsigned &conv); + +private: + void setResolution(const QSize &resolution); + void setFramerate(qreal minFPS, qreal maxFPS, bool useActive); + void setPixelFormat(QVideoFrame::PixelFormat newFormat); + AVCaptureDeviceFormat *findBestFormatMatch(const QCameraViewfinderSettings &settings) const; + QList<QVideoFrame::PixelFormat> viewfinderPixelFormats() const; + bool convertPixelFormatIfSupported(QVideoFrame::PixelFormat format, unsigned &avfFormat) const; + bool applySettings(const QCameraViewfinderSettings &settings); + QCameraViewfinderSettings requestedSettings() const; + + AVCaptureConnection *videoConnection() const; + +private Q_SLOTS: + void updateStatus(); + +private: + friend class AVFCameraSession; + AVFCameraSession *m_session; + AVFCameraService *m_service; + QCameraViewfinderSettings m_settings; + + QCamera::State m_state; + QCamera::Status m_lastStatus; + QCamera::CaptureModes m_captureMode; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h b/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h new file mode 100644 index 000000000..616e53d99 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameradebug_p.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** 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 AVFDEBUG_H +#define AVFDEBUG_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 "qtmultimediaglobal.h" + +#include <QtCore/qdebug.h> + +QT_USE_NAMESPACE + +//#define AVF_DEBUG_CAMERA + +#ifdef AVF_DEBUG_CAMERA +#define qDebugCamera qDebug +#else +#define qDebugCamera QT_NO_QDEBUG_MACRO +#endif + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm new file mode 100644 index 000000000..ac5711fb1 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol.mm @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfcameradevicecontrol_p.h" +#include "avfcameraservice_p.h" +#include "avfcamerasession_p.h" + +QT_USE_NAMESPACE + +AVFCameraDeviceControl::AVFCameraDeviceControl(AVFCameraService *service, QObject *parent) + : QVideoDeviceSelectorControl(parent) + , m_service(service) + , m_selectedDevice(0) + , m_dirty(true) +{ + Q_UNUSED(m_service); +} + +AVFCameraDeviceControl::~AVFCameraDeviceControl() +{ +} + +int AVFCameraDeviceControl::deviceCount() const +{ + return AVFCameraSession::availableCameraDevices().count(); +} + +QString AVFCameraDeviceControl::deviceName(int index) const +{ + const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices(); + if (index < 0 || index >= devices.count()) + return QString(); + + return QString::fromUtf8(devices.at(index).deviceId); +} + +QString AVFCameraDeviceControl::deviceDescription(int index) const +{ + const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices(); + if (index < 0 || index >= devices.count()) + return QString(); + + return devices.at(index).description; +} + +QCamera::Position AVFCameraDeviceControl::cameraPosition(int index) const +{ + const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices(); + if (index < 0 || index >= devices.count()) + return QCamera::UnspecifiedPosition; + + return devices.at(index).position; +} + +int AVFCameraDeviceControl::cameraOrientation(int index) const +{ + const QList<AVFCameraInfo> &devices = AVFCameraSession::availableCameraDevices(); + if (index < 0 || index >= devices.count()) + return 0; + + return devices.at(index).orientation; +} + + +int AVFCameraDeviceControl::defaultDevice() const +{ + return AVFCameraSession::defaultCameraIndex(); +} + +int AVFCameraDeviceControl::selectedDevice() const +{ + return m_selectedDevice; +} + +void AVFCameraDeviceControl::setSelectedDevice(int index) +{ + if (index >= 0 && + index < deviceCount() && + index != m_selectedDevice) { + m_dirty = true; + m_selectedDevice = index; + Q_EMIT selectedDeviceChanged(index); + Q_EMIT selectedDeviceChanged(deviceName(index)); + } +} + +AVCaptureDevice *AVFCameraDeviceControl::createCaptureDevice() +{ + m_dirty = false; + AVCaptureDevice *device = nullptr; + + QString deviceId = deviceName(m_selectedDevice); + if (!deviceId.isEmpty()) { + device = [AVCaptureDevice deviceWithUniqueID: + [NSString stringWithUTF8String: + deviceId.toUtf8().constData()]]; + } + + if (!device) + device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + + return device; +} + +#include "moc_avfcameradevicecontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h new file mode 100644 index 000000000..0fb8628b2 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameradevicecontrol_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** 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 AVFCAMERADEVICECONTROL_H +#define AVFCAMERADEVICECONTROL_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 <QtMultimedia/qvideodeviceselectorcontrol.h> +#include <QtCore/qstringlist.h> + +#import <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; + +class AVFCameraDeviceControl : public QVideoDeviceSelectorControl +{ +Q_OBJECT +public: + AVFCameraDeviceControl(AVFCameraService *service, QObject *parent = nullptr); + ~AVFCameraDeviceControl(); + + int deviceCount() const override; + + QString deviceName(int index) const override; + QString deviceDescription(int index) const override; + QCamera::Position cameraPosition(int index) const override; + int cameraOrientation(int index) const override; + + int defaultDevice() const override; + int selectedDevice() const override; + +public Q_SLOTS: + void setSelectedDevice(int index) override; + +public: + //device changed since the last createCaptureDevice() + bool isDirty() const { return m_dirty; } + AVCaptureDevice *createCaptureDevice(); + +private: + AVFCameraService *m_service; + + int m_selectedDevice; + bool m_dirty; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm new file mode 100644 index 000000000..a0b2ae06d --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol.mm @@ -0,0 +1,831 @@ +/**************************************************************************** +** +** 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 "avfcameraexposurecontrol_p.h" +#include "avfcamerautility_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcameradebug_p.h" + +#include <QtCore/qvariant.h> +#include <QtCore/qpointer.h> +#include <QtCore/qdebug.h> +#include <QtCore/qpair.h> + +#include <AVFoundation/AVFoundation.h> + +#include <limits> + +QT_BEGIN_NAMESPACE + +namespace { + +// All these methods to work with exposure/ISO/SS in custom mode do not support macOS. + +#ifdef Q_OS_IOS + +// Misc. helpers to check values/ranges: + +bool qt_check_ISO_conversion(float isoValue) +{ + if (isoValue >= std::numeric_limits<int>::max()) + return false; + if (isoValue <= std::numeric_limits<int>::min()) + return false; + return true; +} + +bool qt_check_ISO_range(AVCaptureDeviceFormat *format) +{ + // Qt is using int for ISO, AVFoundation - float. It looks like the ISO range + // at the moment can be represented by int (it's max - min > 100, etc.). + Q_ASSERT(format); + if (format.maxISO - format.minISO < 1.) { + // ISO is in some strange units? + return false; + } + + return qt_check_ISO_conversion(format.minISO) + && qt_check_ISO_conversion(format.maxISO); +} + +bool qt_check_exposure_duration(AVCaptureDevice *captureDevice, CMTime duration) +{ + Q_ASSERT(captureDevice); + + AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat; + if (!activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format"; + return false; + } + + return CMTimeCompare(duration, activeFormat.minExposureDuration) != -1 + && CMTimeCompare(activeFormat.maxExposureDuration, duration) != -1; +} + +bool qt_check_ISO_value(AVCaptureDevice *captureDevice, int newISO) +{ + Q_ASSERT(captureDevice); + + AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat; + if (!activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format"; + return false; + } + + return !(newISO < activeFormat.minISO || newISO > activeFormat.maxISO); +} + +bool qt_exposure_duration_equal(AVCaptureDevice *captureDevice, qreal qDuration) +{ + Q_ASSERT(captureDevice); + const CMTime avDuration = CMTimeMakeWithSeconds(qDuration, captureDevice.exposureDuration.timescale); + return !CMTimeCompare(avDuration, captureDevice.exposureDuration); +} + +bool qt_iso_equal(AVCaptureDevice *captureDevice, int iso) +{ + Q_ASSERT(captureDevice); + return qFuzzyCompare(float(iso), captureDevice.ISO); +} + +bool qt_exposure_bias_equal(AVCaptureDevice *captureDevice, qreal bias) +{ + Q_ASSERT(captureDevice); + return qFuzzyCompare(bias, qreal(captureDevice.exposureTargetBias)); +} + +// Converters: + +bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCameraExposure::ExposureMode mode, + AVCaptureExposureMode &avMode) +{ + // Test if mode supported and convert. + Q_ASSERT(captureDevice); + + if (mode == QCameraExposure::ExposureAuto) { + if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) { + avMode = AVCaptureExposureModeContinuousAutoExposure; + return true; + } + } + + if (mode == QCameraExposure::ExposureManual) { + if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom]) { + avMode = AVCaptureExposureModeCustom; + return true; + } + } + + return false; +} + +// We set ISO/exposure duration with completion handlers, completion handlers try +// to avoid dangling pointers (thus QPointer for QObjects) and not to create +// a reference loop (in case we have ARC). + +void qt_set_exposure_bias(QPointer<AVFCameraService> service, QPointer<AVFCameraExposureControl> control, + AVCaptureDevice *captureDevice, float bias) +{ + Q_ASSERT(captureDevice); + + __block AVCaptureDevice *device = captureDevice; //For ARC. + + void (^completionHandler)(CMTime syncTime) = ^(CMTime) { + // Test that service control is still alive and that + // capture device is our device, if yes - emit actual value changed. + if (service) { + if (control) { + if (service->session() && service->session()->videoCaptureDevice() == device) + Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ExposureCompensation)); + } + } + device = nil; + }; + + [captureDevice setExposureTargetBias:bias completionHandler:completionHandler]; +} + +void qt_set_duration_iso(QPointer<AVFCameraService> service, QPointer<AVFCameraExposureControl> control, + AVCaptureDevice *captureDevice, CMTime duration, float iso) +{ + Q_ASSERT(captureDevice); + + __block AVCaptureDevice *device = captureDevice; //For ARC. + const bool setDuration = CMTimeCompare(duration, AVCaptureExposureDurationCurrent); + const bool setISO = !qFuzzyCompare(iso, AVCaptureISOCurrent); + + void (^completionHandler)(CMTime syncTime) = ^(CMTime) { + // Test that service control is still alive and that + // capture device is our device, if yes - emit actual value changed. + if (service) { + if (control) { + if (service->session() && service->session()->videoCaptureDevice() == device) { + if (setDuration) + Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ShutterSpeed)); + if (setISO) + Q_EMIT control->actualValueChanged(int(QCameraExposureControl::ISO)); + } + } + } + device = nil; + }; + + [captureDevice setExposureModeCustomWithDuration:duration + ISO:iso + completionHandler:completionHandler]; +} + +#endif // defined(Q_OS_IOS) + +} // Unnamed namespace. + +AVFCameraExposureControl::AVFCameraExposureControl(AVFCameraService *service) + : m_service(service), + m_session(nullptr) +{ + Q_ASSERT(service); + m_session = m_service->session(); + Q_ASSERT(m_session); + + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(cameraStateChanged(QCamera::State))); +} + +bool AVFCameraExposureControl::isParameterSupported(ExposureParameter parameter) const +{ +#ifdef Q_OS_IOS + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) + return false; + + // These are the parameters we have an API to support: + return parameter == QCameraExposureControl::ISO + || parameter == QCameraExposureControl::ShutterSpeed + || parameter == QCameraExposureControl::ExposureCompensation + || parameter == QCameraExposureControl::ExposureMode; +#else + Q_UNUSED(parameter); + return false; +#endif +} + +QVariantList AVFCameraExposureControl::supportedParameterRange(ExposureParameter parameter, + bool *continuous) const +{ + QVariantList parameterRange; +#ifdef Q_OS_IOS + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice || !isParameterSupported(parameter)) { + qDebugCamera() << Q_FUNC_INFO << "parameter not supported"; + return parameterRange; + } + + if (continuous) + *continuous = false; + + AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat; + + if (parameter == QCameraExposureControl::ISO) { + if (!activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format"; + return parameterRange; + } + + if (!qt_check_ISO_range(activeFormat)) { + qDebugCamera() << Q_FUNC_INFO << "ISO range can not be represented as int"; + return parameterRange; + } + + parameterRange << QVariant(int(activeFormat.minISO)); + parameterRange << QVariant(int(activeFormat.maxISO)); + if (continuous) + *continuous = true; + } else if (parameter == QCameraExposureControl::ExposureCompensation) { + parameterRange << captureDevice.minExposureTargetBias; + parameterRange << captureDevice.maxExposureTargetBias; + if (continuous) + *continuous = true; + } else if (parameter == QCameraExposureControl::ShutterSpeed) { + if (!activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "failed to obtain capture device format"; + return parameterRange; + } + + // CMTimeGetSeconds returns Float64, test the conversion below, if it's valid? + parameterRange << qreal(CMTimeGetSeconds(activeFormat.minExposureDuration)); + parameterRange << qreal(CMTimeGetSeconds(activeFormat.maxExposureDuration)); + + if (continuous) + *continuous = true; + } else if (parameter == QCameraExposureControl::ExposureMode) { + if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom]) + parameterRange << QVariant::fromValue(QCameraExposure::ExposureManual); + + if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) + parameterRange << QVariant::fromValue(QCameraExposure::ExposureAuto); + } +#else + Q_UNUSED(parameter); + Q_UNUSED(continuous); +#endif + return parameterRange; +} + +QVariant AVFCameraExposureControl::requestedValue(ExposureParameter parameter) const +{ + if (!isParameterSupported(parameter)) { + qDebugCamera() << Q_FUNC_INFO << "parameter not supported"; + return QVariant(); + } + + if (parameter == QCameraExposureControl::ExposureMode) + return m_requestedMode; + + if (parameter == QCameraExposureControl::ExposureCompensation) + return m_requestedCompensation; + + if (parameter == QCameraExposureControl::ShutterSpeed) + return m_requestedShutterSpeed; + + if (parameter == QCameraExposureControl::ISO) + return m_requestedISO; + + return QVariant(); +} + +QVariant AVFCameraExposureControl::actualValue(ExposureParameter parameter) const +{ +#ifdef Q_OS_IOS + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice || !isParameterSupported(parameter)) { + // Actually, at the moment !captiredevice => !isParameterSupported. + qDebugCamera() << Q_FUNC_INFO << "parameter not supported"; + return QVariant(); + } + + if (parameter == QCameraExposureControl::ExposureMode) { + // This code expects exposureMode to be continuous by default ... + if (captureDevice.exposureMode == AVCaptureExposureModeContinuousAutoExposure) + return QVariant::fromValue(QCameraExposure::ExposureAuto); + return QVariant::fromValue(QCameraExposure::ExposureManual); + } + + if (parameter == QCameraExposureControl::ExposureCompensation) + return captureDevice.exposureTargetBias; + + if (parameter == QCameraExposureControl::ShutterSpeed) + return qreal(CMTimeGetSeconds(captureDevice.exposureDuration)); + + if (parameter == QCameraExposureControl::ISO) { + if (captureDevice.activeFormat && qt_check_ISO_range(captureDevice.activeFormat) + && qt_check_ISO_conversion(captureDevice.ISO)) { + // Can be represented as int ... + return int(captureDevice.ISO); + } else { + qDebugCamera() << Q_FUNC_INFO << "ISO can not be represented as int"; + return QVariant(); + } + } +#else + Q_UNUSED(parameter); +#endif + return QVariant(); +} + +bool AVFCameraExposureControl::setValue(ExposureParameter parameter, const QVariant &value) +{ + if (parameter == QCameraExposureControl::ExposureMode) + return setExposureMode(value); + else if (parameter == QCameraExposureControl::ExposureCompensation) + return setExposureCompensation(value); + else if (parameter == QCameraExposureControl::ShutterSpeed) + return setShutterSpeed(value); + else if (parameter == QCameraExposureControl::ISO) + return setISO(value); + + return false; +} + +bool AVFCameraExposureControl::setExposureMode(const QVariant &value) +{ +#ifdef Q_OS_IOS + if (!value.canConvert<QCameraExposure::ExposureMode>()) { + qDebugCamera() << Q_FUNC_INFO << "invalid exposure mode value," + << "QCameraExposure::ExposureMode expected"; + return false; + } + + const QCameraExposure::ExposureMode qtMode = value.value<QCameraExposure::ExposureMode>(); + if (qtMode != QCameraExposure::ExposureAuto && qtMode != QCameraExposure::ExposureManual) { + qDebugCamera() << Q_FUNC_INFO << "exposure mode not supported"; + return false; + } + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + m_requestedMode = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureMode)); + return true; + } + + AVCaptureExposureMode avMode = AVCaptureExposureModeAutoExpose; + if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) { + qDebugCamera() << Q_FUNC_INFO << "exposure mode not supported"; + return false; + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock a capture device" + << "for configuration"; + return false; + } + + m_requestedMode = value; + [captureDevice setExposureMode:avMode]; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureMode)); + Q_EMIT actualValueChanged(int(QCameraExposureControl::ExposureMode)); + + return true; +#else + Q_UNUSED(value); + return false; +#endif +} + +bool AVFCameraExposureControl::setExposureCompensation(const QVariant &value) +{ +#ifdef Q_OS_IOS + if (!value.canConvert<qreal>()) { + qDebugCamera() << Q_FUNC_INFO << "invalid exposure compensation" + <<"value, floating point number expected"; + return false; + } + + const qreal bias = value.toReal(); + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + m_requestedCompensation = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureCompensation)); + return true; + } + + if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) { + // TODO: mixed fp types! + qDebugCamera() << Q_FUNC_INFO << "exposure compenstation value is" + << "out of range"; + return false; + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return false; + } + + qt_set_exposure_bias(m_service, this, captureDevice, bias); + m_requestedCompensation = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ExposureCompensation)); + + return true; +#else + Q_UNUSED(value); + return false; +#endif +} + +bool AVFCameraExposureControl::setShutterSpeed(const QVariant &value) +{ +#ifdef Q_OS_IOS + if (value.isNull()) + return setExposureMode(QVariant::fromValue(QCameraExposure::ExposureAuto)); + + if (!value.canConvert<qreal>()) { + qDebugCamera() << Q_FUNC_INFO << "invalid shutter speed" + << "value, floating point number expected"; + return false; + } + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + m_requestedShutterSpeed = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ShutterSpeed)); + return true; + } + + const CMTime newDuration = CMTimeMakeWithSeconds(value.toReal(), + captureDevice.exposureDuration.timescale); + if (!qt_check_exposure_duration(captureDevice, newDuration)) { + qDebugCamera() << Q_FUNC_INFO << "shutter speed value is out of range"; + return false; + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return false; + } + + // Setting the shutter speed (exposure duration in Apple's terms, + // since there is no shutter actually) will also reset + // exposure mode into custom mode. + qt_set_duration_iso(m_service, this, captureDevice, newDuration, AVCaptureISOCurrent); + + m_requestedShutterSpeed = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ShutterSpeed)); + + return true; +#else + Q_UNUSED(value); + return false; +#endif +} + +bool AVFCameraExposureControl::setISO(const QVariant &value) +{ +#ifdef Q_OS_IOS + if (value.isNull()) + return setExposureMode(QVariant::fromValue(QCameraExposure::ExposureAuto)); + + if (!value.canConvert<int>()) { + qDebugCamera() << Q_FUNC_INFO << "invalid ISO value, int expected"; + return false; + } + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + m_requestedISO = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ISO)); + return true; + } + + if (!qt_check_ISO_value(captureDevice, value.toInt())) { + qDebugCamera() << Q_FUNC_INFO << "ISO value is out of range"; + return false; + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock a capture device" + << "for configuration"; + return false; + } + + // Setting the ISO will also reset + // exposure mode to the custom mode. + qt_set_duration_iso(m_service, this, captureDevice, AVCaptureExposureDurationCurrent, value.toInt()); + + m_requestedISO = value; + Q_EMIT requestedValueChanged(int(QCameraExposureControl::ISO)); + + return true; +#else + Q_UNUSED(value); + return false; +#endif +} + +void AVFCameraExposureControl::cameraStateChanged(QCamera::State newState) +{ +#ifdef Q_OS_IOS + if (m_session->state() != QCamera::ActiveState) + return; + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "capture device is nil, but the session" + << "state is 'active'"; + return; + } + + Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ExposureCompensation)); + Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ExposureMode)); + Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ShutterSpeed)); + Q_EMIT parameterRangeChanged(int(QCameraExposureControl::ISO)); + + const AVFConfigurationLock lock(captureDevice); + + CMTime newDuration = AVCaptureExposureDurationCurrent; + bool setCustomMode = false; + + if (!m_requestedShutterSpeed.isNull() + && !qt_exposure_duration_equal(captureDevice, m_requestedShutterSpeed.toReal())) { + newDuration = CMTimeMakeWithSeconds(m_requestedShutterSpeed.toReal(), + captureDevice.exposureDuration.timescale); + if (!qt_check_exposure_duration(captureDevice, newDuration)) { + qDebugCamera() << Q_FUNC_INFO << "requested exposure duration is out of range"; + return; + } + setCustomMode = true; + } + + float newISO = AVCaptureISOCurrent; + if (!m_requestedISO.isNull() && !qt_iso_equal(captureDevice, m_requestedISO.toInt())) { + newISO = m_requestedISO.toInt(); + if (!qt_check_ISO_value(captureDevice, newISO)) { + qDebugCamera() << Q_FUNC_INFO << "requested ISO value is out of range"; + return; + } + setCustomMode = true; + } + + if (!m_requestedCompensation.isNull() + && !qt_exposure_bias_equal(captureDevice, m_requestedCompensation.toReal())) { + // TODO: mixed fpns. + const qreal bias = m_requestedCompensation.toReal(); + if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) { + qDebugCamera() << Q_FUNC_INFO << "exposure compenstation value is" + << "out of range"; + return; + } + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + qt_set_exposure_bias(m_service, this, captureDevice, bias); + } + + // Setting shutter speed (exposure duration) or ISO values + // also reset exposure mode into Custom. With this settings + // we ignore any attempts to set exposure mode. + + if (setCustomMode) { + if (!lock) + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + else + qt_set_duration_iso(m_service, this, captureDevice, newDuration, newISO); + return; + } + + if (!m_requestedMode.isNull()) { + QCameraExposure::ExposureMode qtMode = m_requestedMode.value<QCameraExposure::ExposureMode>(); + AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure; + if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) { + qDebugCamera() << Q_FUNC_INFO << "requested exposure mode is not supported"; + return; + } + + if (avMode == captureDevice.exposureMode) + return; + + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + [captureDevice setExposureMode:avMode]; + Q_EMIT actualValueChanged(int(QCameraExposureControl::ExposureMode)); + } +#endif + + if (newState == QCamera::UnloadedState) { + m_supportedModes = QCameraExposure::FlashOff; + Q_EMIT flashReady(false); + } else if (newState == QCamera::ActiveState) { + m_supportedModes = QCameraExposure::FlashOff; + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "no capture device in 'Active' state"; + Q_EMIT flashReady(false); + return; + } + + if (captureDevice.hasFlash) { + if ([captureDevice isFlashModeSupported:AVCaptureFlashModeOn]) + m_supportedModes |= QCameraExposure::FlashOn; + if ([captureDevice isFlashModeSupported:AVCaptureFlashModeAuto]) + m_supportedModes |= QCameraExposure::FlashAuto; + } + + if (captureDevice.hasTorch && [captureDevice isTorchModeSupported:AVCaptureTorchModeOn]) + m_supportedModes |= QCameraExposure::FlashVideoLight; + + Q_EMIT flashReady(applyFlashSettings()); + } +} + + + +QCameraExposure::FlashModes AVFCameraExposureControl::flashMode() const +{ + return m_flashMode; +} + +void AVFCameraExposureControl::setFlashMode(QCameraExposure::FlashModes mode) +{ + if (m_flashMode == mode) + return; + + if (m_session->state() == QCamera::ActiveState && !isFlashModeSupported(mode)) { + qDebugCamera() << Q_FUNC_INFO << "unsupported mode" << mode; + return; + } + + m_flashMode = mode; + + if (m_session->state() != QCamera::ActiveState) + return; + + applyFlashSettings(); +} + +bool AVFCameraExposureControl::isFlashModeSupported(QCameraExposure::FlashModes mode) const +{ + // From what QCameraExposure has, we can support only these: + // FlashAuto = 0x1, + // FlashOff = 0x2, + // FlashOn = 0x4, + // AVCaptureDevice has these flash modes: + // AVCaptureFlashModeAuto + // AVCaptureFlashModeOff + // AVCaptureFlashModeOn + // QCameraExposure also has: + // FlashTorch = 0x20, --> "Constant light source." + // FlashVideoLight = 0x40. --> "Constant light source." + // AVCaptureDevice: + // AVCaptureTorchModeOff (no mapping) + // AVCaptureTorchModeOn --> FlashVideoLight + // AVCaptureTorchModeAuto (no mapping) + + return m_supportedModes & mode; +} + +bool AVFCameraExposureControl::isFlashReady() const +{ + if (m_session->state() != QCamera::ActiveState) + return false; + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) + return false; + + if (!captureDevice.hasFlash && !captureDevice.hasTorch) + return false; + + if (!isFlashModeSupported(m_flashMode)) + return false; + +#ifdef Q_OS_IOS + // AVCaptureDevice's docs: + // "The flash may become unavailable if, for example, + // the device overheats and needs to cool off." + if (m_flashMode != QCameraExposure::FlashVideoLight) + return [captureDevice isFlashAvailable]; + + return [captureDevice isTorchAvailable]; +#endif + + return true; +} + +bool AVFCameraExposureControl::applyFlashSettings() +{ + Q_ASSERT(m_session->requestedState() == QCamera::ActiveState); + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "no capture device found"; + return false; + } + + if (!isFlashModeSupported(m_flashMode)) { + qDebugCamera() << Q_FUNC_INFO << "unsupported mode" << m_flashMode; + return false; + } + + if (!captureDevice.hasFlash && !captureDevice.hasTorch) { + // FlashOff is the only mode we support. + // Return false - flash is not ready. + return false; + } + + const AVFConfigurationLock lock(captureDevice); + + if (m_flashMode != QCameraExposure::FlashVideoLight) { + if (captureDevice.torchMode != AVCaptureTorchModeOff) { +#ifdef Q_OS_IOS + if (![captureDevice isTorchAvailable]) { + qDebugCamera() << Q_FUNC_INFO << "torch is not available at the moment"; + return false; + } +#endif + captureDevice.torchMode = AVCaptureTorchModeOff; + } +#ifdef Q_OS_IOS + if (![captureDevice isFlashAvailable]) { + // We'd like to switch flash (into some mode), but it's not available: + qDebugCamera() << Q_FUNC_INFO << "flash is not available at the moment"; + return false; + } +#endif + } else { + if (captureDevice.flashMode != AVCaptureFlashModeOff) { +#ifdef Q_OS_IOS + if (![captureDevice isFlashAvailable]) { + qDebugCamera() << Q_FUNC_INFO << "flash is not available at the moment"; + return false; + } +#endif + captureDevice.flashMode = AVCaptureFlashModeOff; + } + +#ifdef Q_OS_IOS + if (![captureDevice isTorchAvailable]) { + qDebugCamera() << Q_FUNC_INFO << "torch is not available at the moment"; + return false; + } +#endif + } + + if (m_flashMode == QCameraExposure::FlashOff) + captureDevice.flashMode = AVCaptureFlashModeOff; + else if (m_flashMode == QCameraExposure::FlashOn) + captureDevice.flashMode = AVCaptureFlashModeOn; + else if (m_flashMode == QCameraExposure::FlashAuto) + captureDevice.flashMode = AVCaptureFlashModeAuto; + else if (m_flashMode == QCameraExposure::FlashVideoLight) + captureDevice.torchMode = AVCaptureTorchModeOn; + + return true; +} + +QT_END_NAMESPACE + +#include "moc_avfcameraexposurecontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_p.h new file mode 100644 index 000000000..9cf60a7b8 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraexposurecontrol_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 AVFCAMERAEXPOSURECONTROL_H +#define AVFCAMERAEXPOSURECONTROL_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 <QtMultimedia/qcameraexposurecontrol.h> +#include <QtMultimedia/qcameraexposure.h> + +#include <QtCore/qglobal.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; + +class AVFCameraExposureControl : public QCameraExposureControl +{ + Q_OBJECT + +public: + AVFCameraExposureControl(AVFCameraService *service); + + 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 cameraStateChanged(QCamera::State newState); + +private: + bool applyFlashSettings(); + + AVFCameraService *m_service; + AVFCameraSession *m_session; + + QVariant m_requestedMode; + QVariant m_requestedCompensation; + QVariant m_requestedShutterSpeed; + QVariant m_requestedISO; + + // Aux. setters: + bool setExposureMode(const QVariant &value); + bool setExposureCompensation(const QVariant &value); + bool setShutterSpeed(const QVariant &value); + bool setISO(const QVariant &value); + + // Set of bits: + QCameraExposure::FlashModes m_supportedModes = QCameraExposure::FlashOff; + // Only one bit set actually: + QCameraExposure::FlashModes m_flashMode = QCameraExposure::FlashOff; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm new file mode 100644 index 000000000..62a7d55e2 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol.mm @@ -0,0 +1,422 @@ +/**************************************************************************** +** +** 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 "avfcamerafocuscontrol_p.h" +#include "avfcamerautility_p.h" +#include "avfcameraservice_p.h" +#include "avfcamerasession_p.h" +#include "avfcameradebug_p.h" + +#include <QtCore/qdebug.h> + +#include <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +namespace { + +bool qt_focus_mode_supported(QCameraFocus::FocusModes mode) +{ + // Check if QCameraFocus::FocusMode has counterpart in AVFoundation. + + // AVFoundation has 'Manual', 'Auto' and 'Continuous', + // where 'Manual' is actually 'Locked' + writable property 'lensPosition'. + // Since Qt does not provide an API to manipulate a lens position, 'Maual' mode + // (at the moment) is not supported. + return mode == QCameraFocus::AutoFocus + || mode == QCameraFocus::ContinuousFocus; +} + +bool qt_focus_point_mode_supported(QCameraFocus::FocusPointMode mode) +{ + return mode == QCameraFocus::FocusPointAuto + || mode == QCameraFocus::FocusPointCustom + || mode == QCameraFocus::FocusPointCenter; +} + +AVCaptureFocusMode avf_focus_mode(QCameraFocus::FocusModes requestedMode) +{ + if (requestedMode == QCameraFocus::AutoFocus) + return AVCaptureFocusModeAutoFocus; + + return AVCaptureFocusModeContinuousAutoFocus; +} + +} + +AVFCameraFocusControl::AVFCameraFocusControl(AVFCameraService *service) + : m_session(service->session()), + m_focusMode(QCameraFocus::ContinuousFocus), + m_focusPointMode(QCameraFocus::FocusPointAuto), + m_customFocusPoint(0.5f, 0.5f), + m_actualFocusPoint(m_customFocusPoint) +{ + Q_ASSERT(m_session); + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(cameraStateChanged())); +} + +QCameraFocus::FocusModes AVFCameraFocusControl::focusMode() const +{ + return m_focusMode; +} + +void AVFCameraFocusControl::setFocusMode(QCameraFocus::FocusModes mode) +{ + if (m_focusMode == mode) + return; + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + if (qt_focus_mode_supported(mode)) { + m_focusMode = mode; + Q_EMIT focusModeChanged(m_focusMode); + } else { + qDebugCamera() << Q_FUNC_INFO + << "focus mode not supported"; + } + return; + } + + if (isFocusModeSupported(mode)) { + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO + << "failed to lock for configuration"; + return; + } + + captureDevice.focusMode = avf_focus_mode(mode); + m_focusMode = mode; + } else { + qDebugCamera() << Q_FUNC_INFO << "focus mode not supported"; + return; + } + + Q_EMIT focusModeChanged(m_focusMode); +} + +bool AVFCameraFocusControl::isFocusModeSupported(QCameraFocus::FocusModes mode) const +{ + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) + return false; + + if (!qt_focus_mode_supported(mode)) + return false; + + return [captureDevice isFocusModeSupported:avf_focus_mode(mode)]; +} + +QCameraFocus::FocusPointMode AVFCameraFocusControl::focusPointMode() const +{ + return m_focusPointMode; +} + +void AVFCameraFocusControl::setFocusPointMode(QCameraFocus::FocusPointMode mode) +{ + if (m_focusPointMode == mode) + return; + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + if (qt_focus_point_mode_supported(mode)) { + m_focusPointMode = mode; + Q_EMIT focusPointModeChanged(mode); + } + return; + } + + if (isFocusPointModeSupported(mode)) { + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + bool resetPOI = false; + if (mode == QCameraFocus::FocusPointCenter || mode == QCameraFocus::FocusPointAuto) { + if (m_actualFocusPoint != QPointF(0.5, 0.5)) { + m_actualFocusPoint = QPointF(0.5, 0.5); + resetPOI = true; + } + } else if (mode == QCameraFocus::FocusPointCustom) { + if (m_actualFocusPoint != m_customFocusPoint) { + m_actualFocusPoint = m_customFocusPoint; + resetPOI = true; + } + } // else for any other mode in future. + + if (resetPOI) { + const CGPoint focusPOI = CGPointMake(m_actualFocusPoint.x(), m_actualFocusPoint.y()); + [captureDevice setFocusPointOfInterest:focusPOI]; + } + m_focusPointMode = mode; + } else { + qDebugCamera() << Q_FUNC_INFO << "focus point mode is not supported"; + return; + } + + Q_EMIT focusPointModeChanged(mode); +} + +bool AVFCameraFocusControl::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const +{ + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) + return false; + + if (!qt_focus_point_mode_supported(mode)) + return false; + + return [captureDevice isFocusPointOfInterestSupported]; +} + +QPointF AVFCameraFocusControl::customFocusPoint() const +{ + return m_customFocusPoint; +} + +void AVFCameraFocusControl::setCustomFocusPoint(const QPointF &point) +{ + if (m_customFocusPoint == point) + return; + + if (!QRectF(0.f, 0.f, 1.f, 1.f).contains(point)) { + qDebugCamera() << Q_FUNC_INFO << "invalid focus point (out of range)"; + return; + } + + m_customFocusPoint = point; + Q_EMIT customFocusPointChanged(m_customFocusPoint); + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice || m_focusPointMode != QCameraFocus::FocusPointCustom) + return; + + if ([captureDevice isFocusPointOfInterestSupported]) { + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + m_actualFocusPoint = m_customFocusPoint; + const CGPoint focusPOI = CGPointMake(point.x(), point.y()); + [captureDevice setFocusPointOfInterest:focusPOI]; + if (m_focusMode != QCameraFocus::ContinuousFocus) + [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus]; + } else { + qDebugCamera() << Q_FUNC_INFO << "focus point of interest not supported"; + return; + } +} + +QCameraFocusZoneList AVFCameraFocusControl::focusZones() const +{ + // Unsupported. + return QCameraFocusZoneList(); +} + +void AVFCameraFocusControl::cameraStateChanged() +{ + if (m_session->state() != QCamera::ActiveState) + return; + + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice) { + qDebugCamera() << Q_FUNC_INFO << "capture device is nil in 'active' state"; + return; + } + + const AVFConfigurationLock lock(captureDevice); + if (m_customFocusPoint != m_actualFocusPoint + && m_focusPointMode == QCameraFocus::FocusPointCustom) { + if (![captureDevice isFocusPointOfInterestSupported]) { + qDebugCamera() << Q_FUNC_INFO + << "focus point of interest not supported"; + return; + } + + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + m_actualFocusPoint = m_customFocusPoint; + const CGPoint focusPOI = CGPointMake(m_customFocusPoint.x(), m_customFocusPoint.y()); + [captureDevice setFocusPointOfInterest:focusPOI]; + } + + if (m_focusMode != QCameraFocus::ContinuousFocus) { + const AVCaptureFocusMode avMode = avf_focus_mode(m_focusMode); + if (captureDevice.focusMode != avMode) { + if (![captureDevice isFocusModeSupported:avMode]) { + qDebugCamera() << Q_FUNC_INFO << "focus mode not supported"; + return; + } + + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + [captureDevice setFocusMode:avMode]; + } + } + +#ifdef Q_OS_IOS + const QCamera::State state = m_session->state(); + if (state != QCamera::ActiveState) { + if (state == QCamera::UnloadedState && m_maxZoomFactor > 1.) { + m_maxZoomFactor = 1.; + Q_EMIT maximumDigitalZoomChanged(1.); + } + return; + } + + if (!captureDevice || !captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "camera state is active, but" + << "video capture device and/or active format is nil"; + return; + } + + if (captureDevice.activeFormat.videoMaxZoomFactor > 1.) { + if (!qFuzzyCompare(m_maxZoomFactor, captureDevice.activeFormat.videoMaxZoomFactor)) { + m_maxZoomFactor = captureDevice.activeFormat.videoMaxZoomFactor; + Q_EMIT maximumDigitalZoomChanged(m_maxZoomFactor); + } + } else if (!qFuzzyCompare(m_maxZoomFactor, CGFloat(1.))) { + m_maxZoomFactor = 1.; + + Q_EMIT maximumDigitalZoomChanged(1.); + } + + zoomToRequestedDigital(); +#endif +} + +qreal AVFCameraFocusControl::maximumOpticalZoom() const +{ + // Not supported. + return 1.; +} + +qreal AVFCameraFocusControl::maximumDigitalZoom() const +{ + return m_maxZoomFactor; +} + +qreal AVFCameraFocusControl::requestedOpticalZoom() const +{ + // Not supported. + return 1; +} + +qreal AVFCameraFocusControl::requestedDigitalZoom() const +{ + return m_requestedZoomFactor; +} + +qreal AVFCameraFocusControl::currentOpticalZoom() const +{ + // Not supported. + return 1.; +} + +qreal AVFCameraFocusControl::currentDigitalZoom() const +{ + return m_zoomFactor; +} + +void AVFCameraFocusControl::zoomTo(qreal optical, qreal digital) +{ + Q_UNUSED(optical); + Q_UNUSED(digital); + +#ifdef QOS_IOS + if (qFuzzyCompare(CGFloat(digital), m_requestedZoomFactor)) + return; + + m_requestedZoomFactor = digital; + Q_EMIT requestedDigitalZoomChanged(digital); + + zoomToRequestedDigital(); +#endif +} + +#ifdef QOS_IOS +void AVFCameraFocusControl::zoomToRequestedDigital() +{ + AVCaptureDevice *captureDevice = m_session->videoCaptureDevice(); + if (!captureDevice || !captureDevice.activeFormat) + return; + + if (qFuzzyCompare(captureDevice.activeFormat.videoMaxZoomFactor, CGFloat(1.))) + return; + + const CGFloat clampedZoom = qBound(CGFloat(1.), m_requestedZoomFactor, + captureDevice.activeFormat.videoMaxZoomFactor); + const CGFloat deviceZoom = captureDevice.videoZoomFactor; + if (qFuzzyCompare(clampedZoom, deviceZoom)) { + // Nothing to set, but check if a signal must be emitted: + if (!qFuzzyCompare(m_zoomFactor, deviceZoom)) { + m_zoomFactor = deviceZoom; + Q_EMIT currentDigitalZoomChanged(deviceZoom); + } + return; + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + captureDevice.videoZoomFactor = clampedZoom; + + if (!qFuzzyCompare(clampedZoom, m_zoomFactor)) { + m_zoomFactor = clampedZoom; + Q_EMIT currentDigitalZoomChanged(clampedZoom); + } +} +#endif + +QT_END_NAMESPACE + +#include "moc_avfcamerafocuscontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h new file mode 100644 index 000000000..3527d48b6 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerafocuscontrol_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef AVFCAMERAFOCUSCONTROL_H +#define AVFCAMERAFOCUSCONTROL_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/qscopedpointer.h> +#include <QtCore/qglobal.h> + +#include <qcamerafocuscontrol.h> + +#include <AVFoundation/AVFoundation.h> + +@class AVCaptureDevice; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; +class AVFCameraSession; + +class AVFCameraFocusControl : public QCameraFocusControl +{ + Q_OBJECT +public: + explicit AVFCameraFocusControl(AVFCameraService *service); + + 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 cameraStateChanged(); + +private: +#ifdef QOS_IOS + void zoomToRequestedDigital(); +#endif + + AVFCameraSession *m_session; + QCameraFocus::FocusModes m_focusMode; + QCameraFocus::FocusPointMode m_focusPointMode; + QPointF m_customFocusPoint; + QPointF m_actualFocusPoint; + + CGFloat m_maxZoomFactor; + CGFloat m_zoomFactor; + CGFloat m_requestedZoomFactor; +}; + + +QT_END_NAMESPACE + +#endif // AVFCAMERAFOCUSCONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm new file mode 100644 index 000000000..4addfd938 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol.mm @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcamerametadatacontrol_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" + +QT_USE_NAMESPACE + +//metadata support is not implemented yet + +AVFCameraMetaDataControl::AVFCameraMetaDataControl(AVFCameraService *service, QObject *parent) + :QMetaDataWriterControl(parent) +{ + Q_UNUSED(service); +} + +AVFCameraMetaDataControl::~AVFCameraMetaDataControl() +{ +} + +bool AVFCameraMetaDataControl::isMetaDataAvailable() const +{ + return !m_tags.isEmpty(); +} + +bool AVFCameraMetaDataControl::isWritable() const +{ + return false; +} + +QVariant AVFCameraMetaDataControl::metaData(const QString &key) const +{ + return m_tags.value(key); +} + +void AVFCameraMetaDataControl::setMetaData(const QString &key, const QVariant &value) +{ + m_tags.insert(key, value); +} + +QStringList AVFCameraMetaDataControl::availableMetaData() const +{ + return m_tags.keys(); +} + +#include "moc_avfcamerametadatacontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_p.h new file mode 100644 index 000000000..2f9138986 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerametadatacontrol_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 AVFCAMERAMETADATACONTROL_H +#define AVFCAMERAMETADATACONTROL_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 <qmetadatawritercontrol.h> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFCameraMetaDataControl : public QMetaDataWriterControl +{ + Q_OBJECT +public: + AVFCameraMetaDataControl(AVFCameraService *service, QObject *parent = nullptr); + virtual ~AVFCameraMetaDataControl(); + + bool isMetaDataAvailable() const override; + bool isWritable() const override; + + QVariant metaData(const QString &key) const override; + void setMetaData(const QString &key, const QVariant &value) override; + QStringList availableMetaData() const override; + +private: + QMap<QString, QVariant> m_tags; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm new file mode 100644 index 000000000..13131766c --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol.mm @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "private/qabstractvideobuffer_p.h" +#include "avfcamerarenderercontrol_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcameradebug_p.h" +#include "avfcameracontrol_p.h" + +#ifdef Q_OS_IOS +#include <QtGui/qopengl.h> +#endif + +#include <QtMultimedia/qabstractvideosurface.h> +#include <QtMultimedia/qabstractvideobuffer.h> + +#include <QtMultimedia/qvideosurfaceformat.h> + +QT_USE_NAMESPACE + +class CVImageVideoBuffer : public QAbstractVideoBuffer +{ +public: + CVImageVideoBuffer(CVImageBufferRef buffer, AVFCameraRendererControl *renderer) +#ifndef Q_OS_IOS + : QAbstractVideoBuffer(NoHandle) +#else + : QAbstractVideoBuffer(renderer->supportsTextures() + && CVPixelBufferGetPixelFormatType(buffer) == kCVPixelFormatType_32BGRA + ? GLTextureHandle : NoHandle) + , m_texture(nullptr) + , m_renderer(renderer) +#endif + , m_buffer(buffer) + , m_mode(NotMapped) + { +#ifndef Q_OS_IOS + Q_UNUSED(renderer); +#endif // Q_OS_IOS + CVPixelBufferRetain(m_buffer); + } + + ~CVImageVideoBuffer() + { + CVImageVideoBuffer::unmap(); +#ifdef Q_OS_IOS + if (m_texture) + CFRelease(m_texture); +#endif + CVPixelBufferRelease(m_buffer); + } + + MapMode mapMode() const { return m_mode; } + + MapData map(QAbstractVideoBuffer::MapMode mode) + { + MapData mapData; + + // We only support RGBA or NV12 (or Apple's version of NV12), + // they are either 0 planes or 2. + mapData.nPlanes = CVPixelBufferGetPlaneCount(m_buffer); + Q_ASSERT(mapData.nPlanes <= 2); + + if (!mapData.nPlanes) { + mapData.data[0] = map(mode, &mapData.nBytes, &mapData.bytesPerLine[0]); + mapData.nPlanes = mapData.data[0] ? 1 : 0; + return mapData; + } + + // For a bi-planar format we have to set the parameters correctly: + if (mode != QAbstractVideoBuffer::NotMapped && m_mode == QAbstractVideoBuffer::NotMapped) { + CVPixelBufferLockBaseAddress(m_buffer, mode == QAbstractVideoBuffer::ReadOnly + ? kCVPixelBufferLock_ReadOnly + : 0); + + mapData.nBytes = CVPixelBufferGetDataSize(m_buffer); + + // At the moment we handle only bi-planar format. + mapData.bytesPerLine[0] = CVPixelBufferGetBytesPerRowOfPlane(m_buffer, 0); + mapData.bytesPerLine[1] = CVPixelBufferGetBytesPerRowOfPlane(m_buffer, 1); + + mapData.data[0] = static_cast<uchar*>(CVPixelBufferGetBaseAddressOfPlane(m_buffer, 0)); + mapData.data[1] = static_cast<uchar*>(CVPixelBufferGetBaseAddressOfPlane(m_buffer, 1)); + + m_mode = mode; + } + + return mapData; + } + + uchar *map(MapMode mode, qsizetype *numBytes, int *bytesPerLine) + { + if (mode != NotMapped && m_mode == NotMapped) { + CVPixelBufferLockBaseAddress(m_buffer, mode == QAbstractVideoBuffer::ReadOnly + ? kCVPixelBufferLock_ReadOnly + : 0); + if (numBytes) + *numBytes = CVPixelBufferGetDataSize(m_buffer); + + if (bytesPerLine) + *bytesPerLine = CVPixelBufferGetBytesPerRow(m_buffer); + + m_mode = mode; + return static_cast<uchar*>(CVPixelBufferGetBaseAddress(m_buffer)); + } else { + return nullptr; + } + } + + void unmap() + { + if (m_mode != NotMapped) { + CVPixelBufferUnlockBaseAddress(m_buffer, m_mode == QAbstractVideoBuffer::ReadOnly + ? kCVPixelBufferLock_ReadOnly + : 0); + m_mode = NotMapped; + } + } + + QVariant handle() const + { +#ifdef Q_OS_IOS + // Called from the render thread, so there is a current OpenGL context + + if (!m_renderer->m_textureCache) { + CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, + nullptr, + [EAGLContext currentContext], + nullptr, + &m_renderer->m_textureCache); + + if (err != kCVReturnSuccess) + qWarning("Error creating texture cache"); + } + + if (m_renderer->m_textureCache && !m_texture) { + CVOpenGLESTextureCacheFlush(m_renderer->m_textureCache, 0); + + CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, + m_renderer->m_textureCache, + m_buffer, + nullptr, + GL_TEXTURE_2D, + GL_RGBA, + CVPixelBufferGetWidth(m_buffer), + CVPixelBufferGetHeight(m_buffer), + GL_RGBA, + GL_UNSIGNED_BYTE, + 0, + &m_texture); + if (err != kCVReturnSuccess) + qWarning("Error creating texture from buffer"); + } + + if (m_texture) + return CVOpenGLESTextureGetName(m_texture); + else + return 0; +#else + return QVariant(); +#endif + } + +private: +#ifdef Q_OS_IOS + mutable CVOpenGLESTextureRef m_texture; + AVFCameraRendererControl *m_renderer; +#endif + CVImageBufferRef m_buffer; + MapMode m_mode; +}; + + +@interface AVFCaptureFramesDelegate : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> + +- (AVFCaptureFramesDelegate *) initWithRenderer:(AVFCameraRendererControl*)renderer; + +- (void) captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection; + +@end + +@implementation AVFCaptureFramesDelegate +{ +@private + AVFCameraRendererControl *m_renderer; +} + +- (AVFCaptureFramesDelegate *) initWithRenderer:(AVFCameraRendererControl*)renderer +{ + if (!(self = [super init])) + return nil; + + self->m_renderer = renderer; + return self; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + Q_UNUSED(connection); + Q_UNUSED(captureOutput); + + // NB: on iOS captureOutput/connection can be nil (when recording a video - + // avfmediaassetwriter). + + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + + int width = CVPixelBufferGetWidth(imageBuffer); + int height = CVPixelBufferGetHeight(imageBuffer); + QVideoFrame::PixelFormat format = + AVFCameraControl::QtPixelFormatFromCVFormat(CVPixelBufferGetPixelFormatType(imageBuffer)); + if (format == QVideoFrame::Format_Invalid) + return; + + QVideoFrame frame(new CVImageVideoBuffer(imageBuffer, m_renderer), + QSize(width, height), + format); + + m_renderer->syncHandleViewfinderFrame(frame); +} + +@end + + +AVFCameraRendererControl::AVFCameraRendererControl(QObject *parent) + : QVideoRendererControl(parent) + , m_surface(nullptr) + , m_supportsTextures(false) + , m_needsHorizontalMirroring(false) +#ifdef Q_OS_IOS + , m_textureCache(nullptr) +#endif +{ + m_viewfinderFramesDelegate = [[AVFCaptureFramesDelegate alloc] initWithRenderer:this]; +} + +AVFCameraRendererControl::~AVFCameraRendererControl() +{ + [m_cameraSession->captureSession() removeOutput:m_videoDataOutput]; + [m_viewfinderFramesDelegate release]; + if (m_delegateQueue) + dispatch_release(m_delegateQueue); +#ifdef Q_OS_IOS + if (m_textureCache) + CFRelease(m_textureCache); +#endif +} + +QAbstractVideoSurface *AVFCameraRendererControl::surface() const +{ + return m_surface; +} + +void AVFCameraRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface != surface) { + m_surface = surface; + m_supportsTextures = m_surface + ? !m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).isEmpty() + : false; + Q_EMIT surfaceChanged(surface); + } +} + +void AVFCameraRendererControl::configureAVCaptureSession(AVFCameraSession *cameraSession) +{ + m_cameraSession = cameraSession; + connect(m_cameraSession, SIGNAL(readyToConfigureConnections()), + this, SLOT(updateCaptureConnection())); + + m_needsHorizontalMirroring = false; + + m_videoDataOutput = [[[AVCaptureVideoDataOutput alloc] init] autorelease]; + + // Configure video output + m_delegateQueue = dispatch_queue_create("vf_queue", nullptr); + [m_videoDataOutput + setSampleBufferDelegate:m_viewfinderFramesDelegate + queue:m_delegateQueue]; + + [m_cameraSession->captureSession() addOutput:m_videoDataOutput]; +} + +void AVFCameraRendererControl::updateCaptureConnection() +{ + AVCaptureConnection *connection = [m_videoDataOutput connectionWithMediaType:AVMediaTypeVideo]; + if (connection == nil || !m_cameraSession->videoCaptureDevice()) + return; + + // Frames of front-facing cameras should be mirrored horizontally (it's the default when using + // AVCaptureVideoPreviewLayer but not with AVCaptureVideoDataOutput) + if (connection.isVideoMirroringSupported) + connection.videoMirrored = m_cameraSession->videoCaptureDevice().position == AVCaptureDevicePositionFront; + + // If the connection does't support mirroring, we'll have to do it ourselves + m_needsHorizontalMirroring = !connection.isVideoMirrored + && m_cameraSession->videoCaptureDevice().position == AVCaptureDevicePositionFront; +} + +//can be called from non main thread +void AVFCameraRendererControl::syncHandleViewfinderFrame(const QVideoFrame &frame) +{ + QMutexLocker lock(&m_vfMutex); + if (!m_lastViewfinderFrame.isValid()) { + static QMetaMethod handleViewfinderFrameSlot = metaObject()->method( + metaObject()->indexOfMethod("handleViewfinderFrame()")); + + handleViewfinderFrameSlot.invoke(this, Qt::QueuedConnection); + } + + m_lastViewfinderFrame = frame; + + if (m_cameraSession && m_lastViewfinderFrame.isValid()) + m_cameraSession->onCameraFrameFetched(m_lastViewfinderFrame); +} + +AVCaptureVideoDataOutput *AVFCameraRendererControl::videoDataOutput() const +{ + return m_videoDataOutput; +} + +#ifdef Q_OS_IOS + +AVFCaptureFramesDelegate *AVFCameraRendererControl::captureDelegate() const +{ + return m_viewfinderFramesDelegate; +} + +void AVFCameraRendererControl::resetCaptureDelegate() const +{ + [m_videoDataOutput setSampleBufferDelegate:m_viewfinderFramesDelegate queue:m_delegateQueue]; +} + +#endif + +void AVFCameraRendererControl::handleViewfinderFrame() +{ + QVideoFrame frame; + { + QMutexLocker lock(&m_vfMutex); + frame = m_lastViewfinderFrame; + m_lastViewfinderFrame = QVideoFrame(); + } + + if (m_surface && frame.isValid()) { + 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(), frame.handleType()); + if (m_needsHorizontalMirroring) + format.setProperty("mirrored", true); + + if (!m_surface->start(format)) { + qWarning() << "Failed to start viewfinder m_surface, format:" << format; + } else { + qDebugCamera() << "Viewfinder started: " << format; + } + } + + if (m_surface->isActive()) + m_surface->present(frame); + } +} + + +#include "moc_avfcamerarenderercontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h new file mode 100644 index 000000000..ade916ed6 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerarenderercontrol_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 AVFCAMERARENDERERCONTROL_H +#define AVFCAMERARENDERERCONTROL_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 <QtMultimedia/qvideorenderercontrol.h> +#include <QtMultimedia/qvideoframe.h> +#include <QtCore/qmutex.h> + +#import <AVFoundation/AVFoundation.h> + +@class AVFCaptureFramesDelegate; + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraService; +class AVFCameraRendererControl; + +class AVFCameraRendererControl : public QVideoRendererControl +{ +Q_OBJECT +public: + AVFCameraRendererControl(QObject *parent = nullptr); + ~AVFCameraRendererControl(); + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + + void configureAVCaptureSession(AVFCameraSession *cameraSession); + void syncHandleViewfinderFrame(const QVideoFrame &frame); + + AVCaptureVideoDataOutput *videoDataOutput() const; + + bool supportsTextures() const { return m_supportsTextures; } + +#ifdef Q_OS_IOS + AVFCaptureFramesDelegate *captureDelegate() const; + void resetCaptureDelegate() const; +#endif + +Q_SIGNALS: + void surfaceChanged(QAbstractVideoSurface *surface); + +private Q_SLOTS: + void handleViewfinderFrame(); + void updateCaptureConnection(); + +private: + QAbstractVideoSurface *m_surface; + AVFCaptureFramesDelegate *m_viewfinderFramesDelegate; + AVFCameraSession *m_cameraSession; + AVCaptureVideoDataOutput *m_videoDataOutput; + + bool m_supportsTextures; + bool m_needsHorizontalMirroring; + +#ifdef Q_OS_IOS + CVOpenGLESTextureCacheRef m_textureCache; +#endif + + QVideoFrame m_lastViewfinderFrame; + QMutex m_vfMutex; + dispatch_queue_t m_delegateQueue; + + friend class CVImageVideoBuffer; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm b/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm new file mode 100644 index 000000000..9fcab0ead --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraservice.mm @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 <QtCore/qvariant.h> +#include <QtCore/qdebug.h> + +#include "avfcameraservice_p.h" +#include "avfcameracontrol_p.h" +#include "avfcamerasession_p.h" +#include "avfcameradevicecontrol_p.h" +#include "avfaudioinputselectorcontrol_p.h" +#include "avfcamerametadatacontrol_p.h" +#include "avfmediarecordercontrol_p.h" +#include "avfimagecapturecontrol_p.h" +#include "avfcamerarenderercontrol_p.h" +#include "avfmediarecordercontrol_p.h" +#include "avfimagecapturecontrol_p.h" +#include "avfmediavideoprobecontrol_p.h" +#include "avfcamerafocuscontrol_p.h" +#include "avfcameraexposurecontrol_p.h" +#include "avfimageencodercontrol_p.h" +#include "avfaudioencodersettingscontrol_p.h" +#include "avfvideoencodersettingscontrol_p.h" +#include "avfmediacontainercontrol_p.h" +#include "avfcamerawindowcontrol_p.h" + +#ifdef Q_OS_IOS +#include "avfmediarecordercontrol_ios_p.h" +#endif + +QT_USE_NAMESPACE + +AVFCameraService::AVFCameraService(QObject *parent): + QMediaService(parent), + m_videoOutput(nullptr), + m_captureWindowControl(nullptr) +{ + m_session = new AVFCameraSession(this); + m_cameraControl = new AVFCameraControl(this); + m_videoDeviceControl = new AVFCameraDeviceControl(this); + m_audioInputSelectorControl = new AVFAudioInputSelectorControl(this); + + m_metaDataControl = new AVFCameraMetaDataControl(this); +#ifndef Q_OS_IOS + // This will connect a slot to 'captureModeChanged' + // and will break viewfinder by attaching AVCaptureMovieFileOutput + // in this slot. + m_recorderControl = new AVFMediaRecorderControl(this); +#else + m_recorderControl = new AVFMediaRecorderControlIOS(this); +#endif + m_imageCaptureControl = new AVFImageCaptureControl(this); + m_cameraFocusControl = new AVFCameraFocusControl(this); + m_cameraExposureControl = nullptr; +#ifdef Q_OS_IOS + m_cameraExposureControl = new AVFCameraExposureControl(this); +#endif + + m_imageEncoderControl = new AVFImageEncoderControl(this); + m_audioEncoderSettingsControl = new AVFAudioEncoderSettingsControl(this); + m_videoEncoderSettingsControl = new AVFVideoEncoderSettingsControl(this); + m_mediaContainerControl = new AVFMediaContainerControl(this); +} + +AVFCameraService::~AVFCameraService() +{ + m_cameraControl->setState(QCamera::UnloadedState); + +#ifdef Q_OS_IOS + delete m_recorderControl; +#endif + + if (m_captureWindowControl) { + m_session->setCapturePreviewOutput(nullptr); + delete m_captureWindowControl; + m_captureWindowControl = nullptr; + } + + if (m_videoOutput) { + m_session->setVideoOutput(nullptr); + delete m_videoOutput; + m_videoOutput = nullptr; + } + + //delete controls before session, + //so they have a chance to do deinitialization + delete m_imageCaptureControl; + //delete m_recorderControl; + delete m_metaDataControl; + delete m_cameraControl; + delete m_cameraFocusControl; + delete m_cameraExposureControl; + delete m_imageEncoderControl; + delete m_audioEncoderSettingsControl; + delete m_videoEncoderSettingsControl; + delete m_mediaContainerControl; + + delete m_session; +} + +QObject *AVFCameraService::requestControl(const char *name) +{ + if (qstrcmp(name, QCameraControl_iid) == 0) + return m_cameraControl; + + if (qstrcmp(name, QVideoDeviceSelectorControl_iid) == 0) + return m_videoDeviceControl; + + if (qstrcmp(name, QAudioInputSelectorControl_iid) == 0) + return m_audioInputSelectorControl; + + //metadata support is not implemented yet + //if (qstrcmp(name, QMetaDataWriterControl_iid) == 0) + // return m_metaDataControl; + + if (qstrcmp(name, QMediaRecorderControl_iid) == 0) + return m_recorderControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + + if (qstrcmp(name, QCameraExposureControl_iid) == 0) + return m_cameraExposureControl; + + if (qstrcmp(name, QCameraFocusControl_iid) == 0) + return m_cameraFocusControl; + + if (qstrcmp(name, QImageEncoderControl_iid) == 0) + return m_imageEncoderControl; + + if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0) + return m_audioEncoderSettingsControl; + + if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) + return m_videoEncoderSettingsControl; + + if (qstrcmp(name, QMediaContainerControl_iid) == 0) + return m_mediaContainerControl; + + if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { + AVFMediaVideoProbeControl *videoProbe = nullptr; + videoProbe = new AVFMediaVideoProbeControl(this); + m_session->addProbe(videoProbe); + return videoProbe; + } + + if (!m_captureWindowControl) { + if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + m_captureWindowControl = new AVFCameraWindowControl(this); + m_session->setCapturePreviewOutput(m_captureWindowControl); + return m_captureWindowControl; + } + } + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoRendererControl_iid) == 0) + m_videoOutput = new AVFCameraRendererControl(this); + + if (m_videoOutput) { + m_session->setVideoOutput(m_videoOutput); + return m_videoOutput; + } + } + + return nullptr; +} + +void AVFCameraService::releaseControl(QObject *control) +{ + AVFMediaVideoProbeControl *videoProbe = qobject_cast<AVFMediaVideoProbeControl *>(control); + if (videoProbe) { + m_session->removeProbe(videoProbe); + delete videoProbe; + } else if (m_videoOutput == control) { + m_session->setVideoOutput(nullptr); + delete m_videoOutput; + m_videoOutput = nullptr; + } + else if (m_captureWindowControl == control) { + m_session->setCapturePreviewOutput(nullptr); + delete m_captureWindowControl; + m_captureWindowControl = nullptr; + } +} + + +#include "moc_avfcameraservice_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h new file mode 100644 index 000000000..9efe4e9cb --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraservice_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 AVFCAMERASERVICE_H +#define AVFCAMERASERVICE_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/qobject.h> +#include <QtCore/qset.h> +#include <qmediaservice.h> + + +QT_BEGIN_NAMESPACE +class QCameraControl; +class QMediaRecorderControl; +class AVFCameraControl; +class AVFCameraMetaDataControl; +class AVFVideoWindowControl; +class AVFVideoWidgetControl; +class AVFCameraRendererControl; +class AVFImageCaptureControl; +class AVFCameraSession; +class AVFCameraDeviceControl; +class AVFAudioInputSelectorControl; +class AVFCameraFocusControl; +class AVFCameraExposureControl; +class AVFImageEncoderControl; +class AVFMediaRecorderControl; +class AVFMediaRecorderControlIOS; +class AVFAudioEncoderSettingsControl; +class AVFVideoEncoderSettingsControl; +class AVFMediaContainerControl; +class AVFCameraWindowControl; + +class AVFCameraService : public QMediaService +{ +Q_OBJECT +public: + AVFCameraService(QObject *parent = nullptr); + ~AVFCameraService(); + + QObject *requestControl(const char *name); + void releaseControl(QObject *control); + + AVFCameraSession *session() const { return m_session; } + AVFCameraControl *cameraControl() const { return m_cameraControl; } + AVFCameraDeviceControl *videoDeviceControl() const { return m_videoDeviceControl; } + AVFAudioInputSelectorControl *audioInputSelectorControl() const { return m_audioInputSelectorControl; } + AVFCameraMetaDataControl *metaDataControl() const { return m_metaDataControl; } + QMediaRecorderControl *recorderControl() const { return m_recorderControl; } + AVFImageCaptureControl *imageCaptureControl() const { return m_imageCaptureControl; } + AVFCameraFocusControl *cameraFocusControl() const { return m_cameraFocusControl; } + AVFCameraExposureControl *cameraExposureControl() const {return m_cameraExposureControl; } + AVFCameraRendererControl *videoOutput() const {return m_videoOutput; } + AVFImageEncoderControl *imageEncoderControl() const {return m_imageEncoderControl; } + AVFAudioEncoderSettingsControl *audioEncoderSettingsControl() const { return m_audioEncoderSettingsControl; } + AVFVideoEncoderSettingsControl *videoEncoderSettingsControl() const {return m_videoEncoderSettingsControl; } + AVFMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } + +private: + AVFCameraSession *m_session; + AVFCameraControl *m_cameraControl; + AVFCameraDeviceControl *m_videoDeviceControl; + AVFAudioInputSelectorControl *m_audioInputSelectorControl; + AVFCameraRendererControl *m_videoOutput; + AVFCameraMetaDataControl *m_metaDataControl; + QMediaRecorderControl *m_recorderControl; + AVFImageCaptureControl *m_imageCaptureControl; + AVFCameraFocusControl *m_cameraFocusControl; + AVFCameraExposureControl *m_cameraExposureControl; + AVFImageEncoderControl *m_imageEncoderControl; + AVFAudioEncoderSettingsControl *m_audioEncoderSettingsControl; + AVFVideoEncoderSettingsControl *m_videoEncoderSettingsControl; + AVFMediaContainerControl *m_mediaContainerControl; + AVFCameraWindowControl *m_captureWindowControl; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm new file mode 100644 index 000000000..2fd8d3fad --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin.mm @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 <QtCore/qstring.h> +#include <QtCore/qdebug.h> + +#include "avfcameraserviceplugin_p.h" +#include "avfcameraservice_p.h" +#include "avfcamerasession_p.h" + +#include <qmediaserviceproviderplugin.h> + +QT_BEGIN_NAMESPACE + +AVFServicePlugin::AVFServicePlugin() +{ +} + +QMediaService* AVFServicePlugin::create(QString const& key) +{ + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA)) + return new AVFCameraService; + else + qWarning() << "unsupported key:" << key; + + return nullptr; +} + +void AVFServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QByteArray AVFServicePlugin::defaultDevice(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA) { + int i = AVFCameraSession::defaultCameraIndex(); + if (i != -1) + return AVFCameraSession::availableCameraDevices().at(i).deviceId; + } + + return QByteArray(); +} + +QList<QByteArray> AVFServicePlugin::devices(const QByteArray &service) const +{ + QList<QByteArray> devs; + + if (service == Q_MEDIASERVICE_CAMERA) { + const QList<AVFCameraInfo> &cameras = AVFCameraSession::availableCameraDevices(); + devs.reserve(cameras.size()); + for (const AVFCameraInfo &info : cameras) + devs.append(info.deviceId); + } + + return devs; +} + +QString AVFServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + if (service == Q_MEDIASERVICE_CAMERA) + return AVFCameraSession::cameraDeviceInfo(device).description; + + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_p.h new file mode 100644 index 000000000..e9028542c --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcameraserviceplugin_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 AVFSERVICEPLUGIN_H +#define AVFSERVICEPLUGIN_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.h> +#include <QtCore/qmap.h> + +QT_BEGIN_NAMESPACE + +class AVFServicePlugin : public QMediaServiceProviderPlugin, + public QMediaServiceSupportedDevicesInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + +public: + AVFServicePlugin(); + + 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 diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm b/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm new file mode 100644 index 000000000..914ae6907 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerasession.mm @@ -0,0 +1,491 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcameracontrol_p.h" +#include "avfcamerarenderercontrol_p.h" +#include "avfcameradevicecontrol_p.h" +#include "avfaudioinputselectorcontrol_p.h" +#include "avfmediavideoprobecontrol_p.h" +#include "avfimageencodercontrol_p.h" +#include "avfcamerautility_p.h" +#include "avfcamerawindowcontrol_p.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <Foundation/Foundation.h> + +#include <QtCore/qdatetime.h> +#include <QtCore/qurl.h> +#include <QtCore/qelapsedtimer.h> + +#include <QtCore/qdebug.h> + +QT_USE_NAMESPACE + +int AVFCameraSession::m_defaultCameraIndex; +QList<AVFCameraInfo> AVFCameraSession::m_cameraDevices; + +@interface AVFCameraSessionObserver : NSObject + +- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session; +- (void) processRuntimeError:(NSNotification *)notification; +- (void) processSessionStarted:(NSNotification *)notification; +- (void) processSessionStopped:(NSNotification *)notification; + +@end + +@implementation AVFCameraSessionObserver +{ +@private + AVFCameraSession *m_session; + AVCaptureSession *m_captureSession; +} + +- (AVFCameraSessionObserver *) initWithCameraSession:(AVFCameraSession*)session +{ + if (!(self = [super init])) + return nil; + + self->m_session = session; + self->m_captureSession = session->captureSession(); + + [m_captureSession retain]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(processRuntimeError:) + name:AVCaptureSessionRuntimeErrorNotification + object:m_captureSession]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(processSessionStarted:) + name:AVCaptureSessionDidStartRunningNotification + object:m_captureSession]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(processSessionStopped:) + name:AVCaptureSessionDidStopRunningNotification + object:m_captureSession]; + + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVCaptureSessionRuntimeErrorNotification + object:m_captureSession]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVCaptureSessionDidStartRunningNotification + object:m_captureSession]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVCaptureSessionDidStopRunningNotification + object:m_captureSession]; + [m_captureSession release]; + [super dealloc]; +} + +- (void) processRuntimeError:(NSNotification *)notification +{ + Q_UNUSED(notification); + QMetaObject::invokeMethod(m_session, "processRuntimeError", Qt::AutoConnection); +} + +- (void) processSessionStarted:(NSNotification *)notification +{ + Q_UNUSED(notification); + QMetaObject::invokeMethod(m_session, "processSessionStarted", Qt::AutoConnection); +} + +- (void) processSessionStopped:(NSNotification *)notification +{ + Q_UNUSED(notification); + QMetaObject::invokeMethod(m_session, "processSessionStopped", Qt::AutoConnection); +} + +@end + +AVFCameraSession::AVFCameraSession(AVFCameraService *service, QObject *parent) + : QObject(parent) + , m_service(service) + , m_capturePreviewWindowOutput(nullptr) + , m_state(QCamera::UnloadedState) + , m_active(false) + , m_videoInput(nil) + , m_defaultCodec(0) +{ + m_captureSession = [[AVCaptureSession alloc] init]; + m_observer = [[AVFCameraSessionObserver alloc] initWithCameraSession:this]; + + //configuration is commited during transition to Active state + [m_captureSession beginConfiguration]; +} + +AVFCameraSession::~AVFCameraSession() +{ + if (m_capturePreviewWindowOutput) { + m_capturePreviewWindowOutput->setLayer(nil); + } + + if (m_videoInput) { + [m_captureSession removeInput:m_videoInput]; + [m_videoInput release]; + } + + [m_observer release]; + [m_captureSession release]; +} + +int AVFCameraSession::defaultCameraIndex() +{ + updateCameraDevices(); + return m_defaultCameraIndex; +} + +const QList<AVFCameraInfo> &AVFCameraSession::availableCameraDevices() +{ + updateCameraDevices(); + return m_cameraDevices; +} + +AVFCameraInfo AVFCameraSession::cameraDeviceInfo(const QByteArray &device) +{ + updateCameraDevices(); + + for (const AVFCameraInfo &info : qAsConst(m_cameraDevices)) { + if (info.deviceId == device) + return info; + } + + return AVFCameraInfo(); +} + +void AVFCameraSession::updateCameraDevices() +{ +#ifdef Q_OS_IOS + // Cameras can't change dynamically on iOS. Update only once. + if (!m_cameraDevices.isEmpty()) + return; +#else + // On OS X, cameras can be added or removed. Update the list every time, but not more than + // once every 500 ms + static QElapsedTimer timer; + if (timer.isValid() && timer.elapsed() < 500) // ms + return; +#endif + + m_defaultCameraIndex = -1; + m_cameraDevices.clear(); + + AVCaptureDevice *defaultDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; + NSArray *videoDevices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + for (AVCaptureDevice *device in videoDevices) { + if (defaultDevice && [defaultDevice.uniqueID isEqualToString:device.uniqueID]) + m_defaultCameraIndex = m_cameraDevices.count(); + + AVFCameraInfo info; + info.deviceId = QByteArray([[device uniqueID] UTF8String]); + info.description = QString::fromNSString([device localizedName]); + + // There is no API to get the camera sensor orientation, however, cameras are always + // mounted in landscape on iDevices. + // - Back-facing cameras have the top side of the sensor aligned with the right side of + // the screen when held in portrait ==> 270 degrees clockwise angle + // - Front-facing cameras have the top side of the sensor aligned with the left side of + // the screen when held in portrait ==> 270 degrees clockwise angle + // On OS X, the position will always be unspecified and the sensor orientation unknown. + switch (device.position) { + case AVCaptureDevicePositionBack: + info.position = QCamera::BackFace; + info.orientation = 270; + break; + case AVCaptureDevicePositionFront: + info.position = QCamera::FrontFace; + info.orientation = 270; + break; + default: + info.position = QCamera::UnspecifiedPosition; + info.orientation = 0; + break; + } + + m_cameraDevices.append(info); + } + +#ifndef Q_OS_IOS + timer.restart(); +#endif +} + +void AVFCameraSession::setVideoOutput(AVFCameraRendererControl *output) +{ + m_videoOutput = output; + if (output) + output->configureAVCaptureSession(this); +} + +void AVFCameraSession::setCapturePreviewOutput(AVFCameraWindowControl *output) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << output; +#endif + + if (m_capturePreviewWindowOutput == output) + return; + + if (m_capturePreviewWindowOutput) + m_capturePreviewWindowOutput->setLayer(nil); + + m_capturePreviewWindowOutput = output; + + if (m_capturePreviewWindowOutput) { + AVCaptureVideoPreviewLayer *previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:m_captureSession]; + m_capturePreviewWindowOutput->setLayer(previewLayer); + if (auto *camera = m_service->cameraControl()) { + m_capturePreviewWindowOutput->setNativeSize(camera->viewfinderSettings().resolution()); + } + } +} + +AVCaptureDevice *AVFCameraSession::videoCaptureDevice() const +{ + if (m_videoInput) + return m_videoInput.device; + + return nullptr; +} + +QCamera::State AVFCameraSession::state() const +{ + if (m_active) + return QCamera::ActiveState; + + return m_state == QCamera::ActiveState ? QCamera::LoadedState : m_state; +} + +void AVFCameraSession::setState(QCamera::State newState) +{ + if (m_state == newState) + return; + + qDebugCamera() << Q_FUNC_INFO << m_state << " -> " << newState; + + QCamera::State oldState = m_state; + m_state = newState; + + //attach video input during Unloaded->Loaded transition + if (oldState == QCamera::UnloadedState) + attachVideoInputDevice(); + + if (m_state == QCamera::ActiveState) { + Q_EMIT readyToConfigureConnections(); + m_defaultCodec = 0; + defaultCodec(); + + bool activeFormatSet = applyImageEncoderSettings() + | applyViewfinderSettings(); + + [m_captureSession commitConfiguration]; + + if (activeFormatSet) { + // According to the doc, the capture device must be locked before + // startRunning to prevent the format we set to be overriden by the + // session preset. + [videoCaptureDevice() lockForConfiguration:nil]; + } + + [m_captureSession startRunning]; + + if (activeFormatSet) + [videoCaptureDevice() unlockForConfiguration]; + } + + if (oldState == QCamera::ActiveState) { + [m_captureSession stopRunning]; + [m_captureSession beginConfiguration]; + } + + Q_EMIT stateChanged(m_state); +} + +void AVFCameraSession::processRuntimeError() +{ + qWarning() << tr("Runtime camera error"); + Q_EMIT error(QCamera::CameraError, tr("Runtime camera error")); +} + +void AVFCameraSession::processSessionStarted() +{ + qDebugCamera() << Q_FUNC_INFO; + if (!m_active) { + m_active = true; + Q_EMIT activeChanged(m_active); + Q_EMIT stateChanged(state()); + } +} + +void AVFCameraSession::processSessionStopped() +{ + qDebugCamera() << Q_FUNC_INFO; + if (m_active) { + m_active = false; + Q_EMIT activeChanged(m_active); + Q_EMIT stateChanged(state()); + } +} + +void AVFCameraSession::onCaptureModeChanged(QCamera::CaptureModes mode) +{ + Q_UNUSED(mode); + + const QCamera::State s = state(); + if (s == QCamera::LoadedState || s == QCamera::ActiveState) { + applyImageEncoderSettings(); + applyViewfinderSettings(); + } +} + +void AVFCameraSession::attachVideoInputDevice() +{ + //Attach video input device: + if (m_service->videoDeviceControl()->isDirty()) { + if (m_videoInput) { + [m_captureSession removeInput:m_videoInput]; + [m_videoInput release]; + m_videoInput = nullptr; + m_activeCameraInfo = AVFCameraInfo(); + } + + AVCaptureDevice *videoDevice = m_service->videoDeviceControl()->createCaptureDevice(); + + NSError *error = nil; + m_videoInput = [AVCaptureDeviceInput + deviceInputWithDevice:videoDevice + error:&error]; + + if (!m_videoInput) { + qWarning() << "Failed to create video device input"; + } else { + if ([m_captureSession canAddInput:m_videoInput]) { + m_activeCameraInfo = m_cameraDevices.at(m_service->videoDeviceControl()->selectedDevice()); + [m_videoInput retain]; + [m_captureSession addInput:m_videoInput]; + } else { + qWarning() << "Failed to connect video device input"; + } + } + } +} + +bool AVFCameraSession::applyImageEncoderSettings() +{ + if (AVFImageEncoderControl *control = m_service->imageEncoderControl()) + return control->applySettings(); + + return false; +} + +bool AVFCameraSession::applyViewfinderSettings() +{ + if (auto *camera = m_service->cameraControl()) { + QCamera::CaptureModes currentMode = m_service->cameraControl()->captureMode(); + QCameraViewfinderSettings vfSettings(camera->requestedSettings()); + // Viewfinder and image capture solutions must be the same, if an image capture + // resolution is set, it takes precedence over the viewfinder resolution. + if (currentMode.testFlag(QCamera::CaptureStillImage)) { + const QSize imageResolution(m_service->imageEncoderControl()->requestedSettings().resolution()); + if (!imageResolution.isNull() && imageResolution.isValid()) + vfSettings.setResolution(imageResolution); + } + + camera->applySettings(vfSettings); + + if (m_capturePreviewWindowOutput) + m_capturePreviewWindowOutput->setNativeSize(camera->viewfinderSettings().resolution()); + + return !vfSettings.isNull(); + } + + return false; +} + +void AVFCameraSession::addProbe(AVFMediaVideoProbeControl *probe) +{ + m_videoProbesMutex.lock(); + if (probe) + m_videoProbes << probe; + m_videoProbesMutex.unlock(); +} + +void AVFCameraSession::removeProbe(AVFMediaVideoProbeControl *probe) +{ + m_videoProbesMutex.lock(); + m_videoProbes.remove(probe); + m_videoProbesMutex.unlock(); +} + +FourCharCode AVFCameraSession::defaultCodec() +{ + if (!m_defaultCodec) { + if (AVCaptureDevice *device = videoCaptureDevice()) { + AVCaptureDeviceFormat *format = device.activeFormat; + if (!format || !format.formatDescription) + return m_defaultCodec; + m_defaultCodec = CMVideoFormatDescriptionGetCodecType(format.formatDescription); + } + } + return m_defaultCodec; +} + +void AVFCameraSession::onCameraFrameFetched(const QVideoFrame &frame) +{ + Q_EMIT newViewfinderFrame(frame); + + m_videoProbesMutex.lock(); + QSet<AVFMediaVideoProbeControl *>::const_iterator i = m_videoProbes.constBegin(); + while (i != m_videoProbes.constEnd()) { + (*i)->newFrameProbed(frame); + ++i; + } + m_videoProbesMutex.unlock(); +} + +#include "moc_avfcamerasession_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h new file mode 100644 index 000000000..19f7945a3 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerasession_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** 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 AVFCAMERASESSION_H +#define AVFCAMERASESSION_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/qmutex.h> +#include <QtMultimedia/qcamera.h> +#include <QVideoFrame> + +#import <AVFoundation/AVFoundation.h> + +@class AVFCameraSessionObserver; + +QT_BEGIN_NAMESPACE + +class AVFCameraControl; +class AVFCameraService; +class AVFCameraRendererControl; +class AVFMediaVideoProbeControl; +class AVFCameraWindowControl; + +struct AVFCameraInfo +{ + AVFCameraInfo() : position(QCamera::UnspecifiedPosition), orientation(0) + { } + + QByteArray deviceId; + QString description; + QCamera::Position position; + int orientation; +}; + +class AVFCameraSession : public QObject +{ + Q_OBJECT +public: + AVFCameraSession(AVFCameraService *service, QObject *parent = nullptr); + ~AVFCameraSession(); + + static int defaultCameraIndex(); + static const QList<AVFCameraInfo> &availableCameraDevices(); + static AVFCameraInfo cameraDeviceInfo(const QByteArray &device); + AVFCameraInfo activeCameraInfo() const { return m_activeCameraInfo; } + + void setVideoOutput(AVFCameraRendererControl *output); + void setCapturePreviewOutput(AVFCameraWindowControl *output); + AVCaptureSession *captureSession() const { return m_captureSession; } + AVCaptureDevice *videoCaptureDevice() const; + + QCamera::State state() const; + QCamera::State requestedState() const { return m_state; } + bool isActive() const { return m_active; } + + void addProbe(AVFMediaVideoProbeControl *probe); + void removeProbe(AVFMediaVideoProbeControl *probe); + FourCharCode defaultCodec(); + + AVCaptureDeviceInput *videoInput() const {return m_videoInput;} + +public Q_SLOTS: + void setState(QCamera::State state); + + void processRuntimeError(); + void processSessionStarted(); + void processSessionStopped(); + + void onCaptureModeChanged(QCamera::CaptureModes mode); + + void onCameraFrameFetched(const QVideoFrame &frame); + +Q_SIGNALS: + void readyToConfigureConnections(); + void stateChanged(QCamera::State newState); + void activeChanged(bool); + void newViewfinderFrame(const QVideoFrame &frame); + void error(int error, const QString &errorString); + +private: + static void updateCameraDevices(); + void attachVideoInputDevice(); + bool applyImageEncoderSettings(); + bool applyViewfinderSettings(); + + static int m_defaultCameraIndex; + static QList<AVFCameraInfo> m_cameraDevices; + AVFCameraInfo m_activeCameraInfo; + + AVFCameraService *m_service; + AVFCameraRendererControl *m_videoOutput; + AVFCameraWindowControl *m_capturePreviewWindowOutput; + + QCamera::State m_state; + bool m_active; + + AVCaptureSession *m_captureSession; + AVCaptureDeviceInput *m_videoInput; + AVFCameraSessionObserver *m_observer; + + QSet<AVFMediaVideoProbeControl *> m_videoProbes; + QMutex m_videoProbesMutex; + + FourCharCode m_defaultCodec; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm b/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm new file mode 100644 index 000000000..fe56517df --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerautility.mm @@ -0,0 +1,575 @@ +/**************************************************************************** +** +** 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 "avfcamerautility_p.h" +#include "avfcameradebug_p.h" + +#include <QtCore/qvector.h> +#include <QtCore/qpair.h> +#include <private/qmultimediautils_p.h> + +#include <functional> +#include <algorithm> +#include <limits> +#include <tuple> + +QT_BEGIN_NAMESPACE + +AVFPSRange qt_connection_framerates(AVCaptureConnection *videoConnection) +{ + Q_ASSERT(videoConnection); + + AVFPSRange newRange; + // "The value in the videoMinFrameDuration is equivalent to the reciprocal + // of the maximum framerate, the value in the videoMaxFrameDuration is equivalent + // to the reciprocal of the minimum framerate." + if (videoConnection.supportsVideoMinFrameDuration) { + const CMTime cmMin = videoConnection.videoMinFrameDuration; + if (CMTimeCompare(cmMin, kCMTimeInvalid)) { // Has some non-default value: + if (const Float64 minSeconds = CMTimeGetSeconds(cmMin)) + newRange.second = 1. / minSeconds; + } + } + + if (videoConnection.supportsVideoMaxFrameDuration) { + const CMTime cmMax = videoConnection.videoMaxFrameDuration; + if (CMTimeCompare(cmMax, kCMTimeInvalid)) { + if (const Float64 maxSeconds = CMTimeGetSeconds(cmMax)) + newRange.first = 1. / maxSeconds; + } + } + + return newRange; +} + +namespace { + +inline bool qt_area_sane(const QSize &size) +{ + return !size.isNull() && size.isValid() + && std::numeric_limits<int>::max() / size.width() >= size.height(); +} + +template <template <typename...> class Comp> // std::less or std::greater (or std::equal_to) +struct ByResolution +{ + bool operator() (AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2)const + { + Q_ASSERT(f1 && f2); + const QSize r1(qt_device_format_resolution(f1)); + const QSize r2(qt_device_format_resolution(f2)); + // use std::tuple for lexicograpical sorting: + const Comp<std::tuple<int, int>> op = {}; + return op(std::make_tuple(r1.width(), r1.height()), + std::make_tuple(r2.width(), r2.height())); + } +}; + +struct FormatHasNoFPSRange : std::unary_function<AVCaptureDeviceFormat *, bool> +{ + bool operator() (AVCaptureDeviceFormat *format) + { + Q_ASSERT(format); + return !format.videoSupportedFrameRateRanges || !format.videoSupportedFrameRateRanges.count; + } +}; + +Float64 qt_find_min_framerate_distance(AVCaptureDeviceFormat *format, Float64 fps) +{ + Q_ASSERT(format && format.videoSupportedFrameRateRanges + && format.videoSupportedFrameRateRanges.count); + + AVFrameRateRange *range = [format.videoSupportedFrameRateRanges objectAtIndex:0]; + Float64 distance = qAbs(range.maxFrameRate - fps); + for (NSUInteger i = 1, e = format.videoSupportedFrameRateRanges.count; i < e; ++i) { + range = [format.videoSupportedFrameRateRanges objectAtIndex:i]; + distance = qMin(distance, qAbs(range.maxFrameRate - fps)); + } + + return distance; +} + +} // Unnamed namespace. + +QVector<AVCaptureDeviceFormat *> qt_unique_device_formats(AVCaptureDevice *captureDevice, FourCharCode filter) +{ + // 'filter' is the format we prefer if we have duplicates. + Q_ASSERT(captureDevice); + + QVector<AVCaptureDeviceFormat *> formats; + + if (!captureDevice.formats || !captureDevice.formats.count) + return formats; + + formats.reserve(captureDevice.formats.count); + for (AVCaptureDeviceFormat *format in captureDevice.formats) { + const QSize resolution(qt_device_format_resolution(format)); + if (resolution.isNull() || !resolution.isValid()) + continue; + formats << format; + } + + if (!formats.size()) + return formats; + + std::sort(formats.begin(), formats.end(), ByResolution<std::less>()); + + QSize size(qt_device_format_resolution(formats[0])); + FourCharCode codec = CMVideoFormatDescriptionGetCodecType(formats[0].formatDescription); + int last = 0; + for (int i = 1; i < formats.size(); ++i) { + const QSize nextSize(qt_device_format_resolution(formats[i])); + if (nextSize == size) { + if (codec == filter) + continue; + formats[last] = formats[i]; + } else { + ++last; + formats[last] = formats[i]; + size = nextSize; + } + codec = CMVideoFormatDescriptionGetCodecType(formats[i].formatDescription); + } + formats.resize(last + 1); + + return formats; +} + +QSize qt_device_format_resolution(AVCaptureDeviceFormat *format) +{ + if (!format || !format.formatDescription) + return QSize(); + + const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + return QSize(res.width, res.height); +} + +QSize qt_device_format_high_resolution(AVCaptureDeviceFormat *format) +{ + Q_ASSERT(format); + QSize res; +#if defined(Q_OS_IOS) + const CMVideoDimensions hrDim(format.highResolutionStillImageDimensions); + res.setWidth(hrDim.width); + res.setHeight(hrDim.height); +#endif + return res; +} + +QVector<AVFPSRange> qt_device_format_framerates(AVCaptureDeviceFormat *format) +{ + Q_ASSERT(format); + + QVector<AVFPSRange> qtRanges; + + if (!format.videoSupportedFrameRateRanges || !format.videoSupportedFrameRateRanges.count) + return qtRanges; + + qtRanges.reserve(format.videoSupportedFrameRateRanges.count); + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) + qtRanges << AVFPSRange(range.minFrameRate, range.maxFrameRate); + + return qtRanges; +} + +QSize qt_device_format_pixel_aspect_ratio(AVCaptureDeviceFormat *format) +{ + Q_ASSERT(format); + + if (!format.formatDescription) { + qDebugCamera() << Q_FUNC_INFO << "no format description found"; + return QSize(); + } + + const CMVideoDimensions res = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + const CGSize resPAR = CMVideoFormatDescriptionGetPresentationDimensions(format.formatDescription, true, false); + + if (qAbs(resPAR.width - res.width) < 1.) { + // "Pixel aspect ratio is used to adjust the width, leaving the height alone." + return QSize(1, 1); + } + + if (!res.width || !resPAR.width) + return QSize(); + + int n, d; + qt_real_to_fraction(resPAR.width > res.width + ? res.width / qreal(resPAR.width) + : resPAR.width / qreal(res.width), + &n, &d); + + return QSize(n, d); +} + +AVCaptureDeviceFormat *qt_find_best_resolution_match(AVCaptureDevice *captureDevice, + const QSize &request, + FourCharCode filter, + bool stillImage) +{ + Q_ASSERT(captureDevice); + Q_ASSERT(!request.isNull() && request.isValid()); + + if (!captureDevice.formats || !captureDevice.formats.count) + return nullptr; + + QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice, filter)); + + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats[i]; + if (qt_device_format_resolution(format) == request) + return format; + // iOS only (still images). + if (stillImage && qt_device_format_high_resolution(format) == request) + return format; + } + + if (!qt_area_sane(request)) + return nullptr; + + typedef QPair<QSize, AVCaptureDeviceFormat *> FormatPair; + + QVector<FormatPair> pairs; // default|HR sizes + pairs.reserve(formats.size()); + + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats[i]; + const QSize res(qt_device_format_resolution(format)); + if (!res.isNull() && res.isValid() && qt_area_sane(res)) + pairs << FormatPair(res, format); + const QSize highRes(qt_device_format_high_resolution(format)); + if (stillImage && !highRes.isNull() && highRes.isValid() && qt_area_sane(highRes)) + pairs << FormatPair(highRes, format); + } + + if (!pairs.size()) + return nullptr; + + AVCaptureDeviceFormat *best = pairs[0].second; + QSize next(pairs[0].first); + int wDiff = qAbs(request.width() - next.width()); + int hDiff = qAbs(request.height() - next.height()); + const int area = request.width() * request.height(); + int areaDiff = qAbs(area - next.width() * next.height()); + for (int i = 1; i < pairs.size(); ++i) { + next = pairs[i].first; + const int newWDiff = qAbs(next.width() - request.width()); + const int newHDiff = qAbs(next.height() - request.height()); + const int newAreaDiff = qAbs(area - next.width() * next.height()); + + if ((newWDiff < wDiff && newHDiff < hDiff) + || ((newWDiff <= wDiff || newHDiff <= hDiff) && newAreaDiff <= areaDiff)) { + wDiff = newWDiff; + hDiff = newHDiff; + best = pairs[i].second; + areaDiff = newAreaDiff; + } + } + + return best; +} + +AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevice, + FourCharCode filter, + Float64 fps) +{ + Q_ASSERT(captureDevice); + Q_ASSERT(fps > 0.); + + const qreal epsilon = 0.1; + + QVector<AVCaptureDeviceFormat *>sorted(qt_unique_device_formats(captureDevice, filter)); + // Sort formats by their resolution in decreasing order: + std::sort(sorted.begin(), sorted.end(), ByResolution<std::greater>()); + // We can use only formats with framerate ranges: + sorted.erase(std::remove_if(sorted.begin(), sorted.end(), FormatHasNoFPSRange()), sorted.end()); + + if (!sorted.size()) + return nil; + + for (int i = 0; i < sorted.size(); ++i) { + AVCaptureDeviceFormat *format = sorted[i]; + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + if (range.maxFrameRate - range.minFrameRate < epsilon) { + // On OS X ranges are points (built-in camera). + if (qAbs(fps - range.maxFrameRate) < epsilon) + return format; + } + + if (fps >= range.minFrameRate && fps <= range.maxFrameRate) + return format; + } + } + + Float64 distance = qt_find_min_framerate_distance(sorted[0], fps); + AVCaptureDeviceFormat *match = sorted[0]; + for (int i = 1; i < sorted.size(); ++i) { + const Float64 newDistance = qt_find_min_framerate_distance(sorted[i], fps); + if (newDistance < distance) { + distance = newDistance; + match = sorted[i]; + } + } + + return match; +} + +AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *format, Float64 fps) +{ + Q_ASSERT(format && format.videoSupportedFrameRateRanges + && format.videoSupportedFrameRateRanges.count); + + const qreal epsilon = 0.1; + + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + if (range.maxFrameRate - range.minFrameRate < epsilon) { + // On OS X ranges are points (built-in camera). + if (qAbs(fps - range.maxFrameRate) < epsilon) + return range; + } + + if (fps >= range.minFrameRate && fps <= range.maxFrameRate) + return range; + } + + AVFrameRateRange *match = [format.videoSupportedFrameRateRanges objectAtIndex:0]; + Float64 distance = qAbs(match.maxFrameRate - fps); + for (NSUInteger i = 1, e = format.videoSupportedFrameRateRanges.count; i < e; ++i) { + AVFrameRateRange *range = [format.videoSupportedFrameRateRanges objectAtIndex:i]; + const Float64 newDistance = qAbs(range.maxFrameRate - fps); + if (newDistance < distance) { + distance = newDistance; + match = range; + } + } + + return match; +} + +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2) +{ + if (f1 == f2) + return true; + + if (![f1.mediaType isEqualToString:f2.mediaType]) + return false; + + return CMFormatDescriptionEqual(f1.formatDescription, f2.formatDescription); +} + +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps) +{ + static bool firstSet = true; + + if (!captureDevice || !format) + return false; + + if (qt_formats_are_equal(captureDevice.activeFormat, format)) { + if (firstSet) { + // The capture device format is persistent. The first time we set a format, report that + // it changed even if the formats are the same. + // This prevents the session from resetting the format to the default value. + firstSet = false; + return true; + } + return false; + } + + firstSet = false; + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qWarning("Failed to set active format (lock failed)"); + return false; + } + + // Changing the activeFormat resets the frame rate. + AVFPSRange fps; + if (preserveFps) + fps = qt_current_framerates(captureDevice, nil); + + captureDevice.activeFormat = format; + + if (preserveFps) + qt_set_framerate_limits(captureDevice, nil, fps.first, fps.second); + + return true; +} + +void qt_set_framerate_limits(AVCaptureConnection *videoConnection, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(videoConnection); + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minDuration = kCMTimeInvalid; + if (maxFPS > 0.) { + if (!videoConnection.supportsVideoMinFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "maximum framerate is not supported"; + else + minDuration = CMTimeMake(1, maxFPS); + } + if (videoConnection.supportsVideoMinFrameDuration) + videoConnection.videoMinFrameDuration = minDuration; + + CMTime maxDuration = kCMTimeInvalid; + if (minFPS > 0.) { + if (!videoConnection.supportsVideoMaxFrameDuration) + qDebugCamera() << Q_FUNC_INFO << "minimum framerate is not supported"; + else + maxDuration = CMTimeMake(1, minFPS); + } + if (videoConnection.supportsVideoMaxFrameDuration) + videoConnection.videoMaxFrameDuration = maxDuration; +} + +CMTime qt_adjusted_frame_duration(AVFrameRateRange *range, qreal fps) +{ + Q_ASSERT(range); + Q_ASSERT(fps > 0.); + + if (range.maxFrameRate - range.minFrameRate < 0.1) { + // Can happen on OS X. + return range.minFrameDuration; + } + + if (fps <= range.minFrameRate) + return range.maxFrameDuration; + if (fps >= range.maxFrameRate) + return range.minFrameDuration; + + int n, d; + qt_real_to_fraction(1. / fps, &n, &d); + return CMTimeMake(n, d); +} + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, qreal minFPS, qreal maxFPS) +{ + Q_ASSERT(captureDevice); + if (!captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active capture device format"; + return; + } + + if (minFPS < 0. || maxFPS < 0. || (maxFPS && maxFPS < minFPS)) { + qDebugCamera() << Q_FUNC_INFO << "invalid framerates (min, max):" + << minFPS << maxFPS; + return; + } + + CMTime minFrameDuration = kCMTimeInvalid; + CMTime maxFrameDuration = kCMTimeInvalid; + if (maxFPS || minFPS) { + AVFrameRateRange *range = qt_find_supported_framerate_range(captureDevice.activeFormat, + maxFPS ? maxFPS : minFPS); + if (!range) { + qDebugCamera() << Q_FUNC_INFO << "no framerate range found, (min, max):" + << minFPS << maxFPS; + return; + } + + if (maxFPS) + minFrameDuration = qt_adjusted_frame_duration(range, maxFPS); + if (minFPS) + maxFrameDuration = qt_adjusted_frame_duration(range, minFPS); + } + + const AVFConfigurationLock lock(captureDevice); + if (!lock) { + qDebugCamera() << Q_FUNC_INFO << "failed to lock for configuration"; + return; + } + + // While Apple's docs say kCMTimeInvalid will end in default + // settings for this format, kCMTimeInvalid on OS X ends with a runtime + // exception: + // "The activeVideoMinFrameDuration passed is not supported by the device." + // Instead, use the first item in the supported frame rates. +#ifdef Q_OS_IOS + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; +#elif defined(Q_OS_MACOS) + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid) == 0 + && CMTimeCompare(maxFrameDuration, kCMTimeInvalid) == 0) { + AVFrameRateRange *range = captureDevice.activeFormat.videoSupportedFrameRateRanges.firstObject; + minFrameDuration = range.minFrameDuration; + maxFrameDuration = range.maxFrameDuration; + } + + if (CMTimeCompare(minFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMinFrameDuration:minFrameDuration]; + + if (CMTimeCompare(maxFrameDuration, kCMTimeInvalid)) + [captureDevice setActiveVideoMaxFrameDuration:maxFrameDuration]; +#endif // Q_OS_MACOS +} + +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS) +{ + Q_UNUSED(videoConnection); + Q_ASSERT(captureDevice); + qt_set_framerate_limits(captureDevice, minFPS, maxFPS); +} + +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection) +{ + Q_UNUSED(videoConnection); + Q_ASSERT(captureDevice); + + AVFPSRange fps; + const CMTime minDuration = captureDevice.activeVideoMinFrameDuration; + if (CMTimeCompare(minDuration, kCMTimeInvalid)) { + if (const Float64 minSeconds = CMTimeGetSeconds(minDuration)) + fps.second = 1. / minSeconds; // Max FPS = 1 / MinDuration. + } + + const CMTime maxDuration = captureDevice.activeVideoMaxFrameDuration; + if (CMTimeCompare(maxDuration, kCMTimeInvalid)) { + if (const Float64 maxSeconds = CMTimeGetSeconds(maxDuration)) + fps.first = 1. / maxSeconds; // Min FPS = 1 / MaxDuration. + } + + return fps; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h new file mode 100644 index 000000000..ffa9630a7 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerautility_p.h @@ -0,0 +1,190 @@ +/**************************************************************************** +** +** 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 AVFCAMERAUTILITY_H +#define AVFCAMERAUTILITY_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/qglobal.h> +#include <QtCore/qdebug.h> +#include <QtCore/qlist.h> +#include <QtCore/qpair.h> +#include <QtCore/qsize.h> + +#include <AVFoundation/AVFoundation.h> + +// In case we have SDK below 10.7/7.0: +@class AVCaptureDeviceFormat; + +QT_BEGIN_NAMESPACE + +class AVFConfigurationLock +{ +public: + explicit AVFConfigurationLock(AVCaptureDevice *captureDevice) + : m_captureDevice(captureDevice), + m_locked(false) + { + Q_ASSERT(m_captureDevice); + NSError *error = nil; + m_locked = [m_captureDevice lockForConfiguration:&error]; + } + + ~AVFConfigurationLock() + { + if (m_locked) + [m_captureDevice unlockForConfiguration]; + } + + operator bool() const + { + return m_locked; + } + +private: + Q_DISABLE_COPY(AVFConfigurationLock) + + AVCaptureDevice *m_captureDevice; + bool m_locked; +}; + +struct AVFObjectDeleter { + static void cleanup(NSObject *obj) + { + if (obj) + [obj release]; + } +}; + +template<class T> +class AVFScopedPointer : public QScopedPointer<NSObject, AVFObjectDeleter> +{ +public: + AVFScopedPointer() {} + explicit AVFScopedPointer(T *ptr) : QScopedPointer(ptr) {} + operator T*() const + { + // Quite handy operator to enable Obj-C messages: [ptr someMethod]; + return data(); + } + + T *data() const + { + return static_cast<T *>(QScopedPointer::data()); + } + + T *take() + { + return static_cast<T *>(QScopedPointer::take()); + } +}; + +template<> +class AVFScopedPointer<dispatch_queue_t> +{ +public: + AVFScopedPointer() : m_queue(nullptr) {} + explicit AVFScopedPointer(dispatch_queue_t q) : m_queue(q) {} + + ~AVFScopedPointer() + { + if (m_queue) + dispatch_release(m_queue); + } + + operator dispatch_queue_t() const + { + // Quite handy operator to enable Obj-C messages: [ptr someMethod]; + return m_queue; + } + + dispatch_queue_t data() const + { + return m_queue; + } + + void reset(dispatch_queue_t q = nullptr) + { + if (m_queue) + dispatch_release(m_queue); + m_queue = q; + } + +private: + dispatch_queue_t m_queue; + + Q_DISABLE_COPY(AVFScopedPointer) +}; + +typedef QPair<qreal, qreal> AVFPSRange; +AVFPSRange qt_connection_framerates(AVCaptureConnection *videoConnection); + +QList<AVCaptureDeviceFormat *> qt_unique_device_formats(AVCaptureDevice *captureDevice, + FourCharCode preferredFormat); +QSize qt_device_format_resolution(AVCaptureDeviceFormat *format); +QSize qt_device_format_high_resolution(AVCaptureDeviceFormat *format); +QSize qt_device_format_pixel_aspect_ratio(AVCaptureDeviceFormat *format); +QList<AVFPSRange> qt_device_format_framerates(AVCaptureDeviceFormat *format); +AVCaptureDeviceFormat *qt_find_best_resolution_match(AVCaptureDevice *captureDevice, const QSize &res, + FourCharCode preferredFormat, bool stillImage = true); +AVCaptureDeviceFormat *qt_find_best_framerate_match(AVCaptureDevice *captureDevice, + FourCharCode preferredFormat, + Float64 fps); +AVFrameRateRange *qt_find_supported_framerate_range(AVCaptureDeviceFormat *format, Float64 fps); + +bool qt_formats_are_equal(AVCaptureDeviceFormat *f1, AVCaptureDeviceFormat *f2); +bool qt_set_active_format(AVCaptureDevice *captureDevice, AVCaptureDeviceFormat *format, bool preserveFps); + +AVFPSRange qt_current_framerates(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection); +void qt_set_framerate_limits(AVCaptureDevice *captureDevice, AVCaptureConnection *videoConnection, + qreal minFPS, qreal maxFPS); + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm new file mode 100644 index 000000000..6c457318d --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol.mm @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcamerawindowcontrol_p.h" + +#import <AVFoundation/AVFoundation.h> +#import <QuartzCore/CATransaction.h> + +#if QT_HAS_INCLUDE(<AppKit/AppKit.h>) +#import <AppKit/AppKit.h> +#endif + +#if QT_HAS_INCLUDE(<UIKit/UIKit.h>) +#import <UIKit/UIKit.h> +#endif + +QT_USE_NAMESPACE + +AVFCameraWindowControl::AVFCameraWindowControl(QObject *parent) + : QVideoWindowControl(parent) +{ + setObjectName(QStringLiteral("AVFCameraWindowControl")); +} + +AVFCameraWindowControl::~AVFCameraWindowControl() +{ + releaseNativeLayer(); +} + +WId AVFCameraWindowControl::winId() const +{ + return m_winId; +} + +void AVFCameraWindowControl::setWinId(WId id) +{ + if (m_winId == id) + return; + + m_winId = id; + + detachNativeLayer(); + m_nativeView = (NativeView*)m_winId; + attachNativeLayer(); +} + +QRect AVFCameraWindowControl::displayRect() const +{ + return m_displayRect; +} + +void AVFCameraWindowControl::setDisplayRect(const QRect &rect) +{ + if (m_displayRect != rect) { + m_displayRect = rect; + updateCaptureLayerBounds(); + } +} + +bool AVFCameraWindowControl::isFullScreen() const +{ + return m_fullscreen; +} + +void AVFCameraWindowControl::setFullScreen(bool fullscreen) +{ + if (m_fullscreen != fullscreen) { + m_fullscreen = fullscreen; + Q_EMIT fullScreenChanged(fullscreen); + } +} + +void AVFCameraWindowControl::repaint() +{ + if (m_captureLayer) + [m_captureLayer setNeedsDisplay]; +} + +QSize AVFCameraWindowControl::nativeSize() const +{ + return m_nativeSize; +} + +void AVFCameraWindowControl::setNativeSize(QSize size) +{ + if (m_nativeSize != size) { + m_nativeSize = size; + Q_EMIT nativeSizeChanged(); + } +} + +Qt::AspectRatioMode AVFCameraWindowControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void AVFCameraWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + if (m_aspectRatioMode != mode) { + m_aspectRatioMode = mode; + updateAspectRatio(); + } +} + +int AVFCameraWindowControl::brightness() const +{ + return 0; +} + +void AVFCameraWindowControl::setBrightness(int brightness) +{ + if (0 != brightness) + qWarning("AVFCameraWindowControl doesn't support changing Brightness"); +} + +int AVFCameraWindowControl::contrast() const +{ + return 0; +} + +void AVFCameraWindowControl::setContrast(int contrast) +{ + if (0 != contrast) + qWarning("AVFCameraWindowControl doesn't support changing Contrast"); +} + +int AVFCameraWindowControl::hue() const +{ + return 0; +} + +void AVFCameraWindowControl::setHue(int hue) +{ + if (0 != hue) + qWarning("AVFCameraWindowControl doesn't support changing Hue"); +} + +int AVFCameraWindowControl::saturation() const +{ + return 0; +} + +void AVFCameraWindowControl::setSaturation(int saturation) +{ + if (0 != saturation) + qWarning("AVFCameraWindowControl doesn't support changing Saturation"); +} + +void AVFCameraWindowControl::setLayer(AVCaptureVideoPreviewLayer *capturePreviewLayer) +{ + if (m_captureLayer == capturePreviewLayer) + return; + + releaseNativeLayer(); + + m_captureLayer = capturePreviewLayer; + + if (m_captureLayer) + retainNativeLayer(); +} + +void AVFCameraWindowControl::updateAspectRatio() +{ + if (m_captureLayer) { + switch (m_aspectRatioMode) { + case Qt::IgnoreAspectRatio: + [m_captureLayer setVideoGravity:AVLayerVideoGravityResize]; + break; + case Qt::KeepAspectRatio: + [m_captureLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; + break; + case Qt::KeepAspectRatioByExpanding: + [m_captureLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; + break; + default: + break; + } + } +} + +void AVFCameraWindowControl::updateCaptureLayerBounds() +{ + if (m_captureLayer && m_nativeView) { + [CATransaction begin]; + [CATransaction setDisableActions: YES]; // disable animation/flicks + m_captureLayer.frame = m_displayRect.toCGRect(); + [CATransaction commit]; + } +} + +void AVFCameraWindowControl::retainNativeLayer() +{ + [m_captureLayer retain]; + + updateAspectRatio(); + attachNativeLayer(); +} + +void AVFCameraWindowControl::releaseNativeLayer() +{ + if (m_captureLayer) { + detachNativeLayer(); + [m_captureLayer release]; + m_captureLayer = nullptr; + } +} + +void AVFCameraWindowControl::attachNativeLayer() +{ + if (m_captureLayer && m_nativeView) { +#if defined(Q_OS_MACOS) + m_nativeView.wantsLayer = YES; +#endif + CALayer *nativeLayer = m_nativeView.layer; + [nativeLayer addSublayer:m_captureLayer]; + updateCaptureLayerBounds(); + } +} + +void AVFCameraWindowControl::detachNativeLayer() +{ + if (m_captureLayer && m_nativeView) + [m_captureLayer removeFromSuperlayer]; +} + +#include "moc_avfcamerawindowcontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h new file mode 100644 index 000000000..d1a950e38 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfcamerawindowcontrol_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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 AVFCAMERAWINDOWCONTROL_H +#define AVFCAMERAWINDOWCONTROL_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 <QVideoWindowControl> + +@class AVCaptureVideoPreviewLayer; +#if defined(Q_OS_MACOS) +@class NSView; +typedef NSView NativeView; +#else +@class UIView; +typedef UIView NativeView; +#endif + +QT_BEGIN_NAMESPACE + +class AVFCameraWindowControl : public QVideoWindowControl +{ + Q_OBJECT +public: + AVFCameraWindowControl(QObject *parent = nullptr); + virtual ~AVFCameraWindowControl() override; + + // QVideoWindowControl interface +public: + WId winId() const override; + void setWinId(WId id) override; + + QRect displayRect() const override; + void setDisplayRect(const QRect &rect) override; + + bool isFullScreen() const override; + void setFullScreen(bool fullScreen) override; + + void repaint() override; + + QSize nativeSize() const override; + + Qt::AspectRatioMode aspectRatioMode() const override; + void setAspectRatioMode(Qt::AspectRatioMode mode) override; + + int brightness() const override; + void setBrightness(int brightness) override; + + int contrast() const override; + void setContrast(int contrast) override; + + int hue() const override; + void setHue(int hue) override; + + int saturation() const override; + void setSaturation(int saturation) override; + + // AVF Camera implementation details + void setNativeSize(QSize size); + void setLayer(AVCaptureVideoPreviewLayer *capturePreviewLayer); + +private: + void updateAspectRatio(); + void updateCaptureLayerBounds(); + + void retainNativeLayer(); + void releaseNativeLayer(); + + void attachNativeLayer(); + void detachNativeLayer(); + + WId m_winId{0}; + QRect m_displayRect; + bool m_fullscreen{false}; + Qt::AspectRatioMode m_aspectRatioMode{Qt::IgnoreAspectRatio}; + QSize m_nativeSize; + AVCaptureVideoPreviewLayer *m_captureLayer{nullptr}; + NativeView *m_nativeView{nullptr}; +}; + +QT_END_NAMESPACE + +#endif // AVFCAMERAWINDOWCONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm new file mode 100644 index 000000000..b019cf2ef --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol.mm @@ -0,0 +1,278 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfimagecapturecontrol_p.h" +#include "avfcameraservice_p.h" +#include "avfcamerautility_p.h" +#include "avfcameracontrol_p.h" +#include <private/qmemoryvideobuffer_p.h> + +#include <QtCore/qurl.h> +#include <QtCore/qfile.h> +#include <QtCore/qbuffer.h> +#include <QtConcurrent/qtconcurrentrun.h> +#include <QtGui/qimagereader.h> + +QT_USE_NAMESPACE + +AVFImageCaptureControl::AVFImageCaptureControl(AVFCameraService *service, QObject *parent) + : QCameraImageCaptureControl(parent) + , m_service(service) + , m_session(service->session()) + , m_cameraControl(service->cameraControl()) + , m_ready(false) + , m_lastCaptureId(0) + , m_videoConnection(nil) +{ + Q_UNUSED(service); + m_stillImageOutput = [[AVCaptureStillImageOutput alloc] init]; + + NSDictionary *outputSettings = [[NSDictionary alloc] initWithObjectsAndKeys: + AVVideoCodecJPEG, AVVideoCodecKey, nil]; + + [m_stillImageOutput setOutputSettings:outputSettings]; + [outputSettings release]; + connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateReadyStatus())); + connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyStatus())); + + connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(updateCaptureConnection())); + connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateCaptureConnection())); + + connect(m_session, &AVFCameraSession::newViewfinderFrame, + this, &AVFImageCaptureControl::onNewViewfinderFrame, + Qt::DirectConnection); +} + +AVFImageCaptureControl::~AVFImageCaptureControl() +{ +} + +bool AVFImageCaptureControl::isReadyForCapture() const +{ + return m_videoConnection && + m_cameraControl->captureMode().testFlag(QCamera::CaptureStillImage) && + m_cameraControl->status() == QCamera::ActiveStatus; +} + +void AVFImageCaptureControl::updateReadyStatus() +{ + if (m_ready != isReadyForCapture()) { + m_ready = !m_ready; + qDebugCamera() << "ReadyToCapture status changed:" << m_ready; + Q_EMIT readyForCaptureChanged(m_ready); + } +} + +int AVFImageCaptureControl::capture(const QString &fileName) +{ + m_lastCaptureId++; + + if (!isReadyForCapture()) { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, m_lastCaptureId), + Q_ARG(int, QCameraImageCapture::NotReadyError), + Q_ARG(QString, tr("Camera not ready"))); + return m_lastCaptureId; + } + + auto destination = m_service->imageCaptureControl()->captureDestination(); + QString actualFileName; + if (destination & QCameraImageCapture::CaptureToFile) { + actualFileName = m_storageLocation.generateFileName(fileName, + QCamera::CaptureStillImage, + QLatin1String("img_"), + QLatin1String("jpg")); + + qDebugCamera() << "Capture image to" << actualFileName; + } + + CaptureRequest request = { m_lastCaptureId, QSharedPointer<QSemaphore>::create()}; + m_requestsMutex.lock(); + m_captureRequests.enqueue(request); + m_requestsMutex.unlock(); + + [m_stillImageOutput captureStillImageAsynchronouslyFromConnection:m_videoConnection + completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error) { + + if (error) { + QStringList messageParts; + messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]); + messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]); + messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]); + + QString errorMessage = messageParts.join(" "); + qDebugCamera() << "Image capture failed:" << errorMessage; + + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(int, QCameraImageCapture::ResourceError), + Q_ARG(QString, errorMessage)); + return; + } + + // Wait for the preview to be generated before saving the JPEG (but only + // if we have AVFCameraRendererControl attached). + // It is possible to stop camera immediately after trying to capture an + // image; this can result in a blocked callback's thread, waiting for a + // new viewfinder's frame to arrive/semaphore to be released. It is also + // unspecified on which thread this callback gets executed, (probably it's + // not the same thread that initiated a capture and stopped the camera), + // so we cannot reliably check the camera's status. Instead, we wait + // with a timeout and treat a failure to acquire a semaphore as an error. + if (!m_service->videoOutput() || request.previewReady->tryAcquire(1, 1000)) { + qDebugCamera() << "Image capture completed"; + + NSData *nsJpgData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer]; + QByteArray jpgData = QByteArray::fromRawData((const char *)[nsJpgData bytes], [nsJpgData length]); + + if (destination & QCameraImageCapture::CaptureToBuffer) { + QBuffer data(&jpgData); + QImageReader reader(&data, "JPEG"); + QSize size = reader.size(); + QVideoFrame frame(new QMemoryVideoBuffer(QByteArray(jpgData.constData(), jpgData.size()), -1), size, QVideoFrame::Format_Jpeg); + QMetaObject::invokeMethod(this, "imageAvailable", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(QVideoFrame, frame)); + } + + if (!(destination & QCameraImageCapture::CaptureToFile)) + return; + + QFile f(actualFileName); + if (f.open(QFile::WriteOnly)) { + if (f.write(jpgData) != -1) { + QMetaObject::invokeMethod(this, "imageSaved", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(QString, actualFileName)); + } else { + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(int, QCameraImageCapture::OutOfSpaceError), + Q_ARG(QString, f.errorString())); + } + } else { + QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(int, QCameraImageCapture::ResourceError), + Q_ARG(QString, errorMessage)); + } + } else { + const QLatin1String errorMessage("Image capture failed: timed out waiting" + " for a preview frame."); + qDebugCamera() << errorMessage; + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, request.captureId), + Q_ARG(int, QCameraImageCapture::ResourceError), + Q_ARG(QString, errorMessage)); + } + }]; + + return request.captureId; +} + +void AVFImageCaptureControl::onNewViewfinderFrame(const QVideoFrame &frame) +{ + QMutexLocker locker(&m_requestsMutex); + + if (m_captureRequests.isEmpty()) + return; + + CaptureRequest request = m_captureRequests.dequeue(); + Q_EMIT imageExposed(request.captureId); + + QtConcurrent::run(&AVFImageCaptureControl::makeCapturePreview, this, + request, + frame, + 0 /* rotation */); +} + +void AVFImageCaptureControl::makeCapturePreview(CaptureRequest request, + const QVideoFrame &frame, + int rotation) +{ + QTransform transform; + transform.rotate(rotation); + + Q_EMIT imageCaptured(request.captureId, frame.image().transformed(transform)); + + request.previewReady->release(); +} + +void AVFImageCaptureControl::cancelCapture() +{ + //not supported +} + +QCameraImageCapture::CaptureDestinations AVFImageCaptureControl::captureDestination() const +{ + return m_destination; +} + +void AVFImageCaptureControl::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + if (m_destination != destination) { + m_destination = destination; + updateCaptureConnection(); + } +} + +void AVFImageCaptureControl::updateCaptureConnection() +{ + if (m_session->videoCaptureDevice() + && m_cameraControl->captureMode().testFlag(QCamera::CaptureStillImage)) { + qDebugCamera() << Q_FUNC_INFO; + AVCaptureSession *captureSession = m_session->captureSession(); + + if (![captureSession.outputs containsObject:m_stillImageOutput]) { + if ([captureSession canAddOutput:m_stillImageOutput]) { + // Lock the video capture device to make sure the active format is not reset + const AVFConfigurationLock lock(m_session->videoCaptureDevice()); + [captureSession addOutput:m_stillImageOutput]; + m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; + updateReadyStatus(); + } + } else { + m_videoConnection = [m_stillImageOutput connectionWithMediaType:AVMediaTypeVideo]; + } + } +} + +#include "moc_avfimagecapturecontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h new file mode 100644 index 000000000..3c781a475 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfimagecapturecontrol_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 AVFIMAGECAPTURECONTROL_H +#define AVFIMAGECAPTURECONTROL_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. +// + +#import <AVFoundation/AVFoundation.h> + +#include <QtCore/qqueue.h> +#include <QtCore/qsemaphore.h> +#include <QtCore/qsharedpointer.h> +#include <QtMultimedia/qcameraimagecapturecontrol.h> +#include "avfcamerasession_p.h" +#include "avfstoragelocation_p.h" + +QT_BEGIN_NAMESPACE + +class AVFImageCaptureControl : public QCameraImageCaptureControl +{ +Q_OBJECT +public: + struct CaptureRequest { + int captureId; + QSharedPointer<QSemaphore> previewReady; + }; + + AVFImageCaptureControl(AVFCameraService *service, QObject *parent = nullptr); + ~AVFImageCaptureControl(); + + bool isReadyForCapture() const override; + + QCameraImageCapture::DriveMode driveMode() const override { return QCameraImageCapture::SingleImageCapture; } + void setDriveMode(QCameraImageCapture::DriveMode ) override {} + AVCaptureStillImageOutput *stillImageOutput() const {return m_stillImageOutput;} + + int capture(const QString &fileName) override; + void cancelCapture() override; + + QCameraImageCapture::CaptureDestinations captureDestination() const override; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) override; + +private Q_SLOTS: + void updateCaptureConnection(); + void updateReadyStatus(); + void onNewViewfinderFrame(const QVideoFrame &frame); + +private: + void makeCapturePreview(CaptureRequest request, const QVideoFrame &frame, int rotation); + + AVFCameraService *m_service; + AVFCameraSession *m_session; + AVFCameraControl *m_cameraControl; + bool m_ready; + int m_lastCaptureId; + AVCaptureStillImageOutput *m_stillImageOutput; + AVCaptureConnection *m_videoConnection; + AVFStorageLocation m_storageLocation; + + QMutex m_requestsMutex; + QQueue<CaptureRequest> m_captureRequests; + + QCameraImageCapture::CaptureDestinations m_destination = QCameraImageCapture::CaptureToFile; +}; + +Q_DECLARE_TYPEINFO(AVFImageCaptureControl::CaptureRequest, Q_PRIMITIVE_TYPE); + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm new file mode 100644 index 000000000..113fdb62b --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol.mm @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 "avfimageencodercontrol_p.h" +#include "avfimagecapturecontrol_p.h" +#include "avfcamerautility_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcameradebug_p.h" +#include "avfcameracontrol_p.h" + +#include <QtMultimedia/qmediaencodersettings.h> + +#include <QtCore/qdebug.h> + +#include <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +AVFImageEncoderControl::AVFImageEncoderControl(AVFCameraService *service) + : m_service(service) +{ + Q_ASSERT(service); +} + +QStringList AVFImageEncoderControl::supportedImageCodecs() const +{ + return QStringList() << QLatin1String("jpeg"); +} + +QString AVFImageEncoderControl::imageCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("jpeg")) + return tr("JPEG image"); + + return QString(); +} + +QList<QSize> AVFImageEncoderControl::supportedResolutions(const QImageEncoderSettings &settings, + bool *continuous) const +{ + Q_UNUSED(settings); + + QList<QSize> resolutions; + + if (!videoCaptureDeviceIsValid()) + return resolutions; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(captureDevice, + m_service->session()->defaultCodec())); + + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats[i]; + + const QSize res(qt_device_format_resolution(format)); + if (!res.isNull() && res.isValid()) + resolutions << res; +#ifdef Q_OS_IOS + // From Apple's docs (iOS): + // By default, AVCaptureStillImageOutput emits images with the same dimensions as + // its source AVCaptureDevice instance’s activeFormat.formatDescription. However, + // if you set this property to YES, the receiver emits still images at the capture + // device’s highResolutionStillImageDimensions value. + const QSize hrRes(qt_device_format_high_resolution(format)); + if (!hrRes.isNull() && hrRes.isValid()) + resolutions << res; +#endif + } + + if (continuous) + *continuous = false; + + return resolutions; +} + +QImageEncoderSettings AVFImageEncoderControl::requestedSettings() const +{ + return m_settings; +} + +QImageEncoderSettings AVFImageEncoderControl::imageSettings() const +{ + QImageEncoderSettings settings; + + if (!videoCaptureDeviceIsValid()) + return settings; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice.activeFormat) { + qDebugCamera() << Q_FUNC_INFO << "no active format"; + return settings; + } + + QSize res(qt_device_format_resolution(captureDevice.activeFormat)); +#ifdef Q_OS_IOS + if (!m_service->imageCaptureControl() || !m_service->imageCaptureControl()->stillImageOutput()) { + qDebugCamera() << Q_FUNC_INFO << "no still image output"; + return settings; + } + + AVCaptureStillImageOutput *stillImageOutput = m_service->imageCaptureControl()->stillImageOutput(); + if (stillImageOutput.highResolutionStillImageOutputEnabled) + res = qt_device_format_high_resolution(captureDevice.activeFormat); +#endif + if (res.isNull() || !res.isValid()) { + qDebugCamera() << Q_FUNC_INFO << "failed to exctract the image resolution"; + return settings; + } + + settings.setResolution(res); + + settings.setCodec(QLatin1String("jpeg")); + + return settings; +} + +void AVFImageEncoderControl::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_settings == settings) + return; + + m_settings = settings; + applySettings(); +} + +bool AVFImageEncoderControl::applySettings() +{ + if (!videoCaptureDeviceIsValid()) + return false; + + AVFCameraSession *session = m_service->session(); + if (!session || (session->state() != QCamera::ActiveState + && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureStillImage)) { + return false; + } + + if (!m_service->imageCaptureControl() + || !m_service->imageCaptureControl()->stillImageOutput()) { + qDebugCamera() << Q_FUNC_INFO << "no still image output"; + return false; + } + + if (m_settings.codec().size() + && m_settings.codec() != QLatin1String("jpeg")) { + qDebugCamera() << Q_FUNC_INFO << "unsupported codec:" << m_settings.codec(); + return false; + } + + QSize res(m_settings.resolution()); + if (res.isNull()) { + qDebugCamera() << Q_FUNC_INFO << "invalid resolution:" << res; + return false; + } + + if (!res.isValid()) { + // Invalid == default value. + // Here we could choose the best format available, but + // activeFormat is already equal to 'preset high' by default, + // which is good enough, otherwise we can end in some format with low framerates. + return false; + } + + bool activeFormatChanged = false; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + AVCaptureDeviceFormat *match = qt_find_best_resolution_match(captureDevice, res, + m_service->session()->defaultCodec()); + + if (!match) { + qDebugCamera() << Q_FUNC_INFO << "unsupported resolution:" << res; + return false; + } + + activeFormatChanged = qt_set_active_format(captureDevice, match, true); + +#ifdef Q_OS_IOS + AVCaptureStillImageOutput *imageOutput = m_service->imageCaptureControl()->stillImageOutput(); + if (res == qt_device_format_high_resolution(captureDevice.activeFormat)) + imageOutput.highResolutionStillImageOutputEnabled = YES; + else + imageOutput.highResolutionStillImageOutputEnabled = NO; +#endif + + return activeFormatChanged; +} + +bool AVFImageEncoderControl::videoCaptureDeviceIsValid() const +{ + if (!m_service->session() || !m_service->session()->videoCaptureDevice()) + return false; + + AVCaptureDevice *captureDevice = m_service->session()->videoCaptureDevice(); + if (!captureDevice.formats || !captureDevice.formats.count) + return false; + + return true; +} + +QT_END_NAMESPACE + +#include "moc_avfimageencodercontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h new file mode 100644 index 000000000..8c2742d35 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfimageencodercontrol_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef AVFIMAGEENCODERCONTROL_H +#define AVFIMAGEENCODERCONTROL_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 <QtMultimedia/qmediaencodersettings.h> +#include <QtMultimedia/qimageencodercontrol.h> + +#include <QtCore/qglobal.h> +#include <QtCore/qstring.h> +#include <QtCore/qlist.h> + +@class AVCaptureDeviceFormat; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFImageEncoderControl : public QImageEncoderControl +{ + Q_OBJECT + + friend class AVFCameraSession; +public: + AVFImageEncoderControl(AVFCameraService *service); + + QStringList supportedImageCodecs() const override; + QString imageCodecDescription(const QString &codecName) const override; + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings, + bool *continuous) const override; + QImageEncoderSettings imageSettings() const override; + void setImageSettings(const QImageEncoderSettings &settings) override; + + QImageEncoderSettings requestedSettings() const; + +private: + AVFCameraService *m_service; + QImageEncoderSettings m_settings; + + bool applySettings(); + bool videoCaptureDeviceIsValid() const; +}; + +QSize qt_image_high_resolution(AVCaptureDeviceFormat *fomat); + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm new file mode 100644 index 000000000..57c5cb8c5 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter.mm @@ -0,0 +1,514 @@ +/**************************************************************************** +** +** 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 "avfaudioinputselectorcontrol.h" +#include "avfmediarecordercontrol_ios.h" +#include "avfcamerarenderercontrol.h" +#include "avfmediaassetwriter.h" +#include "avfcameraservice.h" +#include "avfcamerasession.h" +#include "avfcameradebug.h" +#include "avfmediacontainercontrol.h" + +#include <QtCore/qmetaobject.h> +#include <QtCore/qatomic.h> + +QT_USE_NAMESPACE + +namespace { + +bool qt_camera_service_isValid(AVFCameraService *service) +{ + if (!service || !service->session()) + return false; + + AVFCameraSession *session = service->session(); + if (!session->captureSession()) + return false; + + if (!session->videoInput()) + return false; + + if (!service->videoOutput() + || !service->videoOutput()->videoDataOutput()) { + return false; + } + + return true; +} + +enum WriterState +{ + WriterStateIdle, + WriterStateActive, + WriterStateAborted +}; + +using AVFAtomicInt64 = QAtomicInteger<qint64>; + +} // unnamed namespace + +@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) (PrivateAPI) +- (bool)addAudioCapture; +- (bool)addWriterInputs; +- (void)setQueues; +- (void)updateDuration:(CMTime)newTimeStamp; +@end + +@implementation QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) +{ +@private + AVFCameraService *m_service; + + AVFScopedPointer<AVAssetWriterInput> m_cameraWriterInput; + AVFScopedPointer<AVCaptureDeviceInput> m_audioInput; + AVFScopedPointer<AVCaptureAudioDataOutput> m_audioOutput; + AVFScopedPointer<AVAssetWriterInput> m_audioWriterInput; + + AVCaptureDevice *m_audioCaptureDevice; + + // Queue to write sample buffers: + AVFScopedPointer<dispatch_queue_t> m_writerQueue; + // High priority serial queue for video output: + AVFScopedPointer<dispatch_queue_t> m_videoQueue; + // Serial queue for audio output: + AVFScopedPointer<dispatch_queue_t> m_audioQueue; + + AVFScopedPointer<AVAssetWriter> m_assetWriter; + + AVFMediaRecorderControlIOS *m_delegate; + + bool m_setStartTime; + + QAtomicInt m_state; + + CMTime m_startTime; + CMTime m_lastTimeStamp; + + NSDictionary *m_audioSettings; + NSDictionary *m_videoSettings; + + AVFAtomicInt64 m_durationInMs; +} + +- (id)initWithDelegate:(AVFMediaRecorderControlIOS *)delegate +{ + Q_ASSERT(delegate); + + if (self = [super init]) { + m_delegate = delegate; + m_setStartTime = true; + m_state.storeRelaxed(WriterStateIdle); + m_startTime = kCMTimeInvalid; + m_lastTimeStamp = kCMTimeInvalid; + m_durationInMs.storeRelaxed(0); + m_audioSettings = nil; + m_videoSettings = nil; + } + + return self; +} + +- (bool)setupWithFileURL:(NSURL *)fileURL + cameraService:(AVFCameraService *)service + audioSettings:(NSDictionary *)audioSettings + videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform +{ + Q_ASSERT(fileURL); + + if (!qt_camera_service_isValid(service)) { + qDebugCamera() << Q_FUNC_INFO << "invalid camera service"; + return false; + } + + m_service = service; + m_audioSettings = audioSettings; + m_videoSettings = videoSettings; + + m_writerQueue.reset(dispatch_queue_create("asset-writer-queue", DISPATCH_QUEUE_SERIAL)); + if (!m_writerQueue) { + qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer's queue"; + return false; + } + + m_videoQueue.reset(dispatch_queue_create("video-output-queue", DISPATCH_QUEUE_SERIAL)); + if (!m_videoQueue) { + qDebugCamera() << Q_FUNC_INFO << "failed to create video queue"; + return false; + } + dispatch_set_target_queue(m_videoQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)); + m_audioQueue.reset(dispatch_queue_create("audio-output-queue", DISPATCH_QUEUE_SERIAL)); + if (!m_audioQueue) { + qDebugCamera() << Q_FUNC_INFO << "failed to create audio queue"; + // But we still can write video! + } + + m_assetWriter.reset([[AVAssetWriter alloc] initWithURL:fileURL + fileType:m_service->mediaContainerControl()->fileType() + error:nil]); + if (!m_assetWriter) { + qDebugCamera() << Q_FUNC_INFO << "failed to create asset writer"; + return false; + } + + bool audioCaptureOn = false; + + if (m_audioQueue) + audioCaptureOn = [self addAudioCapture]; + + if (![self addWriterInputs]) { + if (audioCaptureOn) { + AVCaptureSession *session = m_service->session()->captureSession(); + [session removeOutput:m_audioOutput]; + [session removeInput:m_audioInput]; + m_audioOutput.reset(); + m_audioInput.reset(); + m_audioCaptureDevice = 0; + } + m_assetWriter.reset(); + return false; + } + + m_cameraWriterInput.data().transform = transform; + + // Ready to start ... + return true; +} + +- (void)start +{ + [self setQueues]; + + m_setStartTime = true; + + m_state.storeRelease(WriterStateActive); + + [m_assetWriter startWriting]; + AVCaptureSession *session = m_service->session()->captureSession(); + if (!session.running) + [session startRunning]; +} + +- (void)stop +{ + if (m_state.loadAcquire() != WriterStateActive) + return; + + if ([m_assetWriter status] != AVAssetWriterStatusWriting) + return; + + // Do this here so that - + // 1. '-abort' should not try calling finishWriting again and + // 2. async block (see below) will know if recorder control was deleted + // before the block's execution: + m_state.storeRelease(WriterStateIdle); + // Now, since we have to ensure no sample buffers are + // appended after a call to finishWriting, we must + // ensure writer's queue sees this change in m_state + // _before_ we call finishWriting: + dispatch_sync(m_writerQueue, ^{}); + // Done, but now we also want to prevent video queue + // from updating our viewfinder: + dispatch_sync(m_videoQueue, ^{}); + + // Now we're safe to stop: + [m_assetWriter finishWritingWithCompletionHandler:^{ + // This block is async, so by the time it's executed, + // it's possible that render control was deleted already ... + if (m_state.loadAcquire() == WriterStateAborted) + return; + + AVCaptureSession *session = m_service->session()->captureSession(); + if (session.running) + [session stopRunning]; + [session removeOutput:m_audioOutput]; + [session removeInput:m_audioInput]; + QMetaObject::invokeMethod(m_delegate, "assetWriterFinished", Qt::QueuedConnection); + }]; +} + +- (void)abort +{ + // -abort is to be called from recorder control's dtor. + + if (m_state.fetchAndStoreRelease(WriterStateAborted) != WriterStateActive) { + // Not recording, nothing to stop. + return; + } + + // From Apple's docs: + // "To guarantee that all sample buffers are successfully written, + // you must ensure that all calls to appendSampleBuffer: and + // appendPixelBuffer:withPresentationTime: have returned before + // invoking this method." + // + // The only way we can ensure this is: + dispatch_sync(m_writerQueue, ^{}); + // At this point next block (if any) on the writer's queue + // will see m_state preventing it from any further processing. + dispatch_sync(m_videoQueue, ^{}); + // After this point video queue will not try to modify our + // viewfider, so we're safe to delete now. + + [m_assetWriter finishWritingWithCompletionHandler:^{ + }]; +} + +- (void)setStartTimeFrom:(CMSampleBufferRef)sampleBuffer +{ + // Writer's queue only. + Q_ASSERT(m_setStartTime); + Q_ASSERT(sampleBuffer); + + if (m_state.loadAcquire() != WriterStateActive) + return; + + QMetaObject::invokeMethod(m_delegate, "assetWriterStarted", Qt::QueuedConnection); + + m_durationInMs.storeRelease(0); + m_startTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + m_lastTimeStamp = m_startTime; + [m_assetWriter startSessionAtSourceTime:m_startTime]; + m_setStartTime = false; +} + +- (void)writeVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer +{ + // This code is executed only on a writer's queue. + Q_ASSERT(sampleBuffer); + + if (m_state.loadAcquire() == WriterStateActive) { + if (m_setStartTime) + [self setStartTimeFrom:sampleBuffer]; + + if (m_cameraWriterInput.data().readyForMoreMediaData) { + [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)]; + [m_cameraWriterInput appendSampleBuffer:sampleBuffer]; + } + } + + CFRelease(sampleBuffer); +} + +- (void)writeAudioSampleBuffer:(CMSampleBufferRef)sampleBuffer +{ + Q_ASSERT(sampleBuffer); + + // This code is executed only on a writer's queue. + if (m_state.loadAcquire() == WriterStateActive) { + if (m_setStartTime) + [self setStartTimeFrom:sampleBuffer]; + + if (m_audioWriterInput.data().readyForMoreMediaData) { + [self updateDuration:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)]; + [m_audioWriterInput appendSampleBuffer:sampleBuffer]; + } + } + + CFRelease(sampleBuffer); +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + Q_UNUSED(connection); + + if (m_state.loadAcquire() != WriterStateActive) + return; + + if (!CMSampleBufferDataIsReady(sampleBuffer)) { + qDebugCamera() << Q_FUNC_INFO << "sample buffer is not ready, skipping."; + return; + } + + CFRetain(sampleBuffer); + + if (captureOutput != m_audioOutput.data()) { + if (m_state.loadRelaxed() != WriterStateActive) { + CFRelease(sampleBuffer); + return; + } + // Find renderercontrol's delegate and invoke its method to + // show updated viewfinder's frame. + if (m_service && m_service->videoOutput()) { + NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *vfDelegate = + (NSObject<AVCaptureVideoDataOutputSampleBufferDelegate> *)m_service->videoOutput()->captureDelegate(); + if (vfDelegate) + [vfDelegate captureOutput:nil didOutputSampleBuffer:sampleBuffer fromConnection:nil]; + } + + dispatch_async(m_writerQueue, ^{ + [self writeVideoSampleBuffer:sampleBuffer]; + }); + } else { + dispatch_async(m_writerQueue, ^{ + [self writeAudioSampleBuffer:sampleBuffer]; + }); + } +} + +- (bool)addAudioCapture +{ + Q_ASSERT(m_service && m_service->session() && m_service->session()->captureSession()); + + if (!m_service->audioInputSelectorControl()) + return false; + + AVCaptureSession *captureSession = m_service->session()->captureSession(); + + m_audioCaptureDevice = m_service->audioInputSelectorControl()->createCaptureDevice(); + if (!m_audioCaptureDevice) { + qWarning() << Q_FUNC_INFO << "no audio input device available"; + return false; + } else { + NSError *error = nil; + m_audioInput.reset([[AVCaptureDeviceInput deviceInputWithDevice:m_audioCaptureDevice error:&error] retain]); + + if (!m_audioInput || error) { + qWarning() << Q_FUNC_INFO << "failed to create audio device input"; + m_audioCaptureDevice = 0; + m_audioInput.reset(); + return false; + } else if (![captureSession canAddInput:m_audioInput]) { + qWarning() << Q_FUNC_INFO << "could not connect the audio input"; + m_audioCaptureDevice = 0; + m_audioInput.reset(); + return false; + } else { + [captureSession addInput:m_audioInput]; + } + } + + + m_audioOutput.reset([[AVCaptureAudioDataOutput alloc] init]); + if (m_audioOutput.data() && [captureSession canAddOutput:m_audioOutput]) { + [captureSession addOutput:m_audioOutput]; + } else { + qDebugCamera() << Q_FUNC_INFO << "failed to add audio output"; + [captureSession removeInput:m_audioInput]; + m_audioCaptureDevice = 0; + m_audioInput.reset(); + m_audioOutput.reset(); + return false; + } + + return true; +} + +- (bool)addWriterInputs +{ + Q_ASSERT(m_service && m_service->videoOutput() + && m_service->videoOutput()->videoDataOutput()); + Q_ASSERT(m_assetWriter.data()); + + m_cameraWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeVideo + outputSettings:m_videoSettings + sourceFormatHint:m_service->session()->videoCaptureDevice().activeFormat.formatDescription]); + if (!m_cameraWriterInput) { + qDebugCamera() << Q_FUNC_INFO << "failed to create camera writer input"; + return false; + } + + if ([m_assetWriter canAddInput:m_cameraWriterInput]) { + [m_assetWriter addInput:m_cameraWriterInput]; + } else { + qDebugCamera() << Q_FUNC_INFO << "failed to add camera writer input"; + m_cameraWriterInput.reset(); + return false; + } + + m_cameraWriterInput.data().expectsMediaDataInRealTime = YES; + + if (m_audioOutput.data()) { + CMFormatDescriptionRef sourceFormat = m_audioCaptureDevice ? m_audioCaptureDevice.activeFormat.formatDescription : 0; + m_audioWriterInput.reset([[AVAssetWriterInput alloc] initWithMediaType:AVMediaTypeAudio + outputSettings:m_audioSettings + sourceFormatHint:sourceFormat]); + if (!m_audioWriterInput) { + qDebugCamera() << Q_FUNC_INFO << "failed to create audio writer input"; + // But we still can record video. + } else if ([m_assetWriter canAddInput:m_audioWriterInput]) { + [m_assetWriter addInput:m_audioWriterInput]; + m_audioWriterInput.data().expectsMediaDataInRealTime = YES; + } else { + qDebugCamera() << Q_FUNC_INFO << "failed to add audio writer input"; + m_audioWriterInput.reset(); + // We can (still) write video though ... + } + } + + return true; +} + +- (void)setQueues +{ + Q_ASSERT(m_service && m_service->videoOutput() && m_service->videoOutput()->videoDataOutput()); + Q_ASSERT(m_videoQueue); + + [m_service->videoOutput()->videoDataOutput() setSampleBufferDelegate:self queue:m_videoQueue]; + + if (m_audioOutput.data()) { + Q_ASSERT(m_audioQueue); + [m_audioOutput setSampleBufferDelegate:self queue:m_audioQueue]; + } +} + +- (void)updateDuration:(CMTime)newTimeStamp +{ + Q_ASSERT(CMTimeCompare(m_startTime, kCMTimeInvalid)); + Q_ASSERT(CMTimeCompare(m_lastTimeStamp, kCMTimeInvalid)); + if (CMTimeCompare(newTimeStamp, m_lastTimeStamp) > 0) { + + const CMTime duration = CMTimeSubtract(newTimeStamp, m_startTime); + if (!CMTimeCompare(duration, kCMTimeInvalid)) + return; + + m_durationInMs.storeRelease(CMTimeGetSeconds(duration) * 1000); + m_lastTimeStamp = newTimeStamp; + } +} + +- (qint64)durationInMs +{ + return m_durationInMs.loadAcquire(); +} + +@end diff --git a/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_p.h new file mode 100644 index 000000000..61d7d46bc --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediaassetwriter_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 AVFMEDIAASSETWRITER_H +#define AVFMEDIAASSETWRITER_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 "avfcamerautility.h" + +#include <QtCore/qglobal.h> + +#include <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +class AVFMediaRecorderControlIOS; +class AVFCameraService; + +QT_END_NAMESPACE + +@interface QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) : NSObject<AVCaptureVideoDataOutputSampleBufferDelegate, + AVCaptureAudioDataOutputSampleBufferDelegate> +- (id)initWithDelegate:(QT_PREPEND_NAMESPACE(AVFMediaRecorderControlIOS) *)delegate; + +- (bool)setupWithFileURL:(NSURL *)fileURL + cameraService:(QT_PREPEND_NAMESPACE(AVFCameraService) *)service + audioSettings:(NSDictionary *)audioSettings + videoSettings:(NSDictionary *)videoSettings + transform:(CGAffineTransform)transform; + +// This to be called from the recorder control's thread: +- (void)start; +- (void)stop; +// This to be called from the recorder control's dtor: +- (void)abort; +- (qint64)durationInMs; + +@end + +#endif // AVFMEDIAASSETWRITER_H diff --git a/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm new file mode 100644 index 000000000..09049de0b --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol.mm @@ -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$ +** +****************************************************************************/ + +#include "avfmediacontainercontrol_p.h" + +#include <AVFoundation/AVMediaFormat.h> +#include <QtCore/qmap.h> + +QT_BEGIN_NAMESPACE + +struct ContainerInfo +{ + QString description; + NSString *fileType; + + ContainerInfo() : fileType(nil) { } + ContainerInfo(const QString &desc, NSString *type) + : description(desc), fileType(type) + { } +}; + +typedef QMap<QString, ContainerInfo> SupportedContainers; +Q_GLOBAL_STATIC(SupportedContainers, containers); + +AVFMediaContainerControl::AVFMediaContainerControl(AVFCameraService *) + : QMediaContainerControl() + , m_format(QStringLiteral("mov")) // .mov is the default container format on Apple platforms +{ + if (containers->isEmpty()) { + containers->insert(QStringLiteral("mov"), + ContainerInfo(QStringLiteral("QuickTime movie file format"), + AVFileTypeQuickTimeMovie)); + containers->insert(QStringLiteral("mp4"), + ContainerInfo(QStringLiteral("MPEG-4 file format"), + AVFileTypeMPEG4)); + containers->insert(QStringLiteral("m4v"), + ContainerInfo(QStringLiteral("iTunes video file format"), + AVFileTypeAppleM4V)); +#ifdef Q_OS_IOS + containers->insert(QStringLiteral("3gp"), + ContainerInfo(QStringLiteral("3GPP file format"), + AVFileType3GPP)); +#endif + } +} + +QStringList AVFMediaContainerControl::supportedContainers() const +{ + return containers->keys(); +} + +QString AVFMediaContainerControl::containerFormat() const +{ + return m_format; +} + +void AVFMediaContainerControl::setContainerFormat(const QString &format) +{ + if (!containers->contains(format)) { + qWarning("Unsupported container format: '%s'", format.toLocal8Bit().constData()); + return; + } + + m_format = format; +} + +QString AVFMediaContainerControl::containerDescription(const QString &formatMimeType) const +{ + return containers->value(formatMimeType).description; +} + +NSString *AVFMediaContainerControl::fileType() const +{ + return containers->value(m_format).fileType; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h new file mode 100644 index 000000000..9450dc16a --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediacontainercontrol_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 AVFMEDIACONTAINERCONTROL_H +#define AVFMEDIACONTAINERCONTROL_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> + +@class NSString; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFMediaContainerControl : public QMediaContainerControl +{ +public: + explicit AVFMediaContainerControl(AVFCameraService *service); + + QStringList supportedContainers() const override; + QString containerFormat() const override; + void setContainerFormat(const QString &format) override; + QString containerDescription(const QString &formatMimeType) const override; + + NSString *fileType() const; + +private: + QString m_format; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIACONTAINERCONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm new file mode 100644 index 000000000..27f78fea4 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol.mm @@ -0,0 +1,430 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfcameradebug_p.h" +#include "avfmediarecordercontrol_p.h" +#include "avfcamerasession_p.h" +#include "avfcameraservice_p.h" +#include "avfcameracontrol_p.h" +#include "avfaudioinputselectorcontrol_p.h" +#include "avfaudioencodersettingscontrol_p.h" +#include "avfvideoencodersettingscontrol_p.h" +#include "avfmediacontainercontrol_p.h" + +#include <QtCore/qurl.h> +#include <QtCore/qfileinfo.h> +#include <QtMultimedia/qcameracontrol.h> + + +QT_USE_NAMESPACE + +@interface AVFMediaRecorderDelegate : NSObject <AVCaptureFileOutputRecordingDelegate> +{ +@private + AVFMediaRecorderControl *m_recorder; +} + +- (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder; + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didStartRecordingToOutputFileAtURL:(NSURL *)fileURL + fromConnections:(NSArray *)connections; + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL + fromConnections:(NSArray *)connections + error:(NSError *)error; +@end + +@implementation AVFMediaRecorderDelegate + +- (AVFMediaRecorderDelegate *) initWithRecorder:(AVFMediaRecorderControl*)recorder +{ + if (!(self = [super init])) + return nil; + + self->m_recorder = recorder; + return self; +} + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didStartRecordingToOutputFileAtURL:(NSURL *)fileURL + fromConnections:(NSArray *)connections +{ + Q_UNUSED(captureOutput); + Q_UNUSED(fileURL); + Q_UNUSED(connections); + + QMetaObject::invokeMethod(m_recorder, "handleRecordingStarted", Qt::QueuedConnection); +} + +- (void) captureOutput:(AVCaptureFileOutput *)captureOutput + didFinishRecordingToOutputFileAtURL:(NSURL *)fileURL + fromConnections:(NSArray *)connections + error:(NSError *)error +{ + Q_UNUSED(captureOutput); + Q_UNUSED(fileURL); + Q_UNUSED(connections); + + if (error) { + QStringList messageParts; + messageParts << QString::fromUtf8([[error localizedDescription] UTF8String]); + messageParts << QString::fromUtf8([[error localizedFailureReason] UTF8String]); + messageParts << QString::fromUtf8([[error localizedRecoverySuggestion] UTF8String]); + + QString errorMessage = messageParts.join(" "); + + QMetaObject::invokeMethod(m_recorder, "handleRecordingFailed", Qt::QueuedConnection, + Q_ARG(QString, errorMessage)); + } else { + QMetaObject::invokeMethod(m_recorder, "handleRecordingFinished", Qt::QueuedConnection); + } +} + +@end + + +AVFMediaRecorderControl::AVFMediaRecorderControl(AVFCameraService *service, QObject *parent) + : QMediaRecorderControl(parent) + , m_service(service) + , m_cameraControl(service->cameraControl()) + , m_audioInputControl(service->audioInputSelectorControl()) + , m_session(service->session()) + , m_connected(false) + , m_state(QMediaRecorder::StoppedState) + , m_lastStatus(QMediaRecorder::UnloadedStatus) + , m_recordingStarted(false) + , m_recordingFinished(false) + , m_muted(false) + , m_volume(1.0) + , m_audioInput(nil) + , m_restoreFPS(-1, -1) +{ + m_movieOutput = [[AVCaptureMovieFileOutput alloc] init]; + m_recorderDelagate = [[AVFMediaRecorderDelegate alloc] initWithRecorder:this]; + + connect(m_cameraControl, SIGNAL(stateChanged(QCamera::State)), SLOT(updateStatus())); + connect(m_cameraControl, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateStatus())); + connect(m_cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(setupSessionForCapture())); + connect(m_session, SIGNAL(readyToConfigureConnections()), SLOT(setupSessionForCapture())); + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(setupSessionForCapture())); +} + +AVFMediaRecorderControl::~AVFMediaRecorderControl() +{ + if (m_movieOutput) { + [m_session->captureSession() removeOutput:m_movieOutput]; + [m_movieOutput release]; + } + + if (m_audioInput) { + [m_session->captureSession() removeInput:m_audioInput]; + [m_audioInput release]; + } + + [m_recorderDelagate release]; +} + +QUrl AVFMediaRecorderControl::outputLocation() const +{ + return m_outputLocation; +} + +bool AVFMediaRecorderControl::setOutputLocation(const QUrl &location) +{ + m_outputLocation = location; + return location.scheme() == QLatin1String("file") || location.scheme().isEmpty(); +} + +QMediaRecorder::State AVFMediaRecorderControl::state() const +{ + return m_state; +} + +QMediaRecorder::Status AVFMediaRecorderControl::status() const +{ + QMediaRecorder::Status status = m_lastStatus; + //bool videoEnabled = m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo); + + if (m_cameraControl->status() == QCamera::ActiveStatus && m_connected) { + if (m_state == QMediaRecorder::StoppedState) { + if (m_recordingStarted && !m_recordingFinished) + status = QMediaRecorder::FinalizingStatus; + else + status = QMediaRecorder::LoadedStatus; + } else { + status = m_recordingStarted ? QMediaRecorder::RecordingStatus : + QMediaRecorder::StartingStatus; + } + } else { + //camera not started yet + status = m_cameraControl->state() == QCamera::ActiveState && m_connected ? + QMediaRecorder::LoadingStatus: + QMediaRecorder::UnloadedStatus; + } + + return status; +} + +void AVFMediaRecorderControl::updateStatus() +{ + QMediaRecorder::Status newStatus = status(); + + if (m_lastStatus != newStatus) { + qDebugCamera() << "Camera recorder status changed: " << m_lastStatus << " -> " << newStatus; + m_lastStatus = newStatus; + Q_EMIT statusChanged(m_lastStatus); + } +} + + +qint64 AVFMediaRecorderControl::duration() const +{ + if (!m_movieOutput) + return 0; + + return qint64(CMTimeGetSeconds(m_movieOutput.recordedDuration) * 1000); +} + +bool AVFMediaRecorderControl::isMuted() const +{ + return m_muted; +} + +qreal AVFMediaRecorderControl::volume() const +{ + return m_volume; +} + +void AVFMediaRecorderControl::applySettings() +{ + if (m_state != QMediaRecorder::StoppedState + || (m_session->state() != QCamera::ActiveState && m_session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + // Configure audio settings + [m_movieOutput setOutputSettings:m_service->audioEncoderSettingsControl()->applySettings() + forConnection:[m_movieOutput connectionWithMediaType:AVMediaTypeAudio]]; + + // Configure video settings + AVCaptureConnection *videoConnection = [m_movieOutput connectionWithMediaType:AVMediaTypeVideo]; + NSDictionary *videoSettings = m_service->videoEncoderSettingsControl()->applySettings(videoConnection); + + const AVFConfigurationLock lock(m_session->videoCaptureDevice()); // prevents activeFormat from being overridden + + [m_movieOutput setOutputSettings:videoSettings forConnection:videoConnection]; +} + +void AVFMediaRecorderControl::unapplySettings() +{ + m_service->audioEncoderSettingsControl()->unapplySettings(); + m_service->videoEncoderSettingsControl()->unapplySettings([m_movieOutput connectionWithMediaType:AVMediaTypeVideo]); +} + +void AVFMediaRecorderControl::setState(QMediaRecorder::State state) +{ + if (m_state == state) + return; + + qDebugCamera() << Q_FUNC_INFO << m_state << " -> " << state; + + switch (state) { + case QMediaRecorder::RecordingState: + { + if (m_connected) { + QString outputLocationPath = m_outputLocation.scheme() == QLatin1String("file") ? + m_outputLocation.path() : m_outputLocation.toString(); + + QString extension = m_service->mediaContainerControl()->containerFormat(); + + QUrl actualLocation = QUrl::fromLocalFile( + m_storageLocation.generateFileName(outputLocationPath, + QCamera::CaptureVideo, + QLatin1String("clip_"), + extension)); + + qDebugCamera() << "Video capture location:" << actualLocation.toString(); + + applySettings(); + + [m_movieOutput startRecordingToOutputFileURL:actualLocation.toNSURL() + recordingDelegate:m_recorderDelagate]; + + m_state = QMediaRecorder::RecordingState; + m_recordingStarted = false; + m_recordingFinished = false; + + Q_EMIT actualLocationChanged(actualLocation); + updateStatus(); + Q_EMIT stateChanged(m_state); + } else { + Q_EMIT error(QMediaRecorder::FormatError, tr("Recorder not configured")); + } + + } break; + case QMediaRecorder::PausedState: + { + Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported")); + return; + } break; + case QMediaRecorder::StoppedState: + { + m_lastStatus = QMediaRecorder::FinalizingStatus; + Q_EMIT statusChanged(m_lastStatus); + [m_movieOutput stopRecording]; + unapplySettings(); + } + } +} + +void AVFMediaRecorderControl::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + Q_EMIT mutedChanged(muted); + } +} + +void AVFMediaRecorderControl::setVolume(qreal volume) +{ + if (m_volume != volume) { + m_volume = volume; + Q_EMIT volumeChanged(volume); + } +} + +void AVFMediaRecorderControl::handleRecordingStarted() +{ + m_recordingStarted = true; + updateStatus(); +} + +void AVFMediaRecorderControl::handleRecordingFinished() +{ + m_recordingFinished = true; + if (m_state != QMediaRecorder::StoppedState) { + m_state = QMediaRecorder::StoppedState; + Q_EMIT stateChanged(m_state); + } + updateStatus(); +} + +void AVFMediaRecorderControl::handleRecordingFailed(const QString &message) +{ + m_recordingFinished = true; + if (m_state != QMediaRecorder::StoppedState) { + m_state = QMediaRecorder::StoppedState; + Q_EMIT stateChanged(m_state); + } + updateStatus(); + + Q_EMIT error(QMediaRecorder::ResourceError, message); +} + +void AVFMediaRecorderControl::setupSessionForCapture() +{ + if (!m_session->videoCaptureDevice()) + return; + + //adding movie output causes high CPU usage even when while recording is not active, + //connect it only while video capture mode is enabled. + // Similarly, connect the Audio input only in that mode, since it's only necessary + // when recording anyway. Adding an Audio input will trigger the microphone permission + // request on iOS, but it shoudn't do so until we actually try to record. + AVCaptureSession *captureSession = m_session->captureSession(); + + if (!m_connected + && m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo) + && m_session->state() != QCamera::UnloadedState) { + + // Lock the video capture device to make sure the active format is not reset + const AVFConfigurationLock lock(m_session->videoCaptureDevice()); + + // Add audio input + // Allow recording even if something wrong happens with the audio input initialization + AVCaptureDevice *audioDevice = m_audioInputControl->createCaptureDevice(); + if (!audioDevice) { + qWarning("No audio input device available"); + } else { + NSError *error = nil; + m_audioInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error]; + + if (!m_audioInput) { + qWarning() << "Failed to create audio device input"; + } else if (![captureSession canAddInput:m_audioInput]) { + qWarning() << "Could not connect the audio input"; + m_audioInput = nullptr; + } else { + [m_audioInput retain]; + [captureSession addInput:m_audioInput]; + } + } + + if ([captureSession canAddOutput:m_movieOutput]) { + [captureSession addOutput:m_movieOutput]; + m_connected = true; + } else { + Q_EMIT error(QMediaRecorder::ResourceError, tr("Could not connect the video recorder")); + qWarning() << "Could not connect the video recorder"; + } + } else if (m_connected + && (!m_cameraControl->captureMode().testFlag(QCamera::CaptureVideo) + || m_session->state() == QCamera::UnloadedState)) { + + // Lock the video capture device to make sure the active format is not reset + const AVFConfigurationLock lock(m_session->videoCaptureDevice()); + + [captureSession removeOutput:m_movieOutput]; + + if (m_audioInput) { + [captureSession removeInput:m_audioInput]; + [m_audioInput release]; + m_audioInput = nil; + } + + m_connected = false; + } + + updateStatus(); +} + +#include "moc_avfmediarecordercontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm new file mode 100644 index 000000000..33064827d --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios.mm @@ -0,0 +1,414 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediarecordercontrol_ios.h" +#include "avfcamerarenderercontrol.h" +#include "avfcamerasession.h" +#include "avfcameracontrol.h" +#include "avfcameraservice.h" +#include "avfcameradebug.h" +#include "avfaudioencodersettingscontrol.h" +#include "avfvideoencodersettingscontrol.h" +#include "avfmediacontainercontrol.h" +#include "avfcamerautility.h" + +#include <QtCore/qmath.h> +#include <QtCore/qdebug.h> + +QT_USE_NAMESPACE + +namespace { + +bool qt_is_writable_file_URL(NSURL *fileURL) +{ + Q_ASSERT(fileURL); + + if (![fileURL isFileURL]) + return false; + + if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) { + return [[NSFileManager defaultManager] + isWritableFileAtPath:[path stringByDeletingLastPathComponent]]; + } + + return false; +} + +bool qt_file_exists(NSURL *fileURL) +{ + Q_ASSERT(fileURL); + + if (NSString *path = [[fileURL path] stringByExpandingTildeInPath]) + return [[NSFileManager defaultManager] fileExistsAtPath:path]; + + return false; +} + +} + +AVFMediaRecorderControlIOS::AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent) + : QMediaRecorderControl(parent) + , m_service(service) + , m_state(QMediaRecorder::StoppedState) + , m_lastStatus(QMediaRecorder::UnloadedStatus) + , m_audioSettings(nil) + , m_videoSettings(nil) +{ + Q_ASSERT(service); + + m_writer.reset([[QT_MANGLE_NAMESPACE(AVFMediaAssetWriter) alloc] initWithDelegate:this]); + if (!m_writer) { + qDebugCamera() << Q_FUNC_INFO << "failed to create an asset writer"; + return; + } + + AVFCameraControl *cameraControl = m_service->cameraControl(); + if (!cameraControl) { + qDebugCamera() << Q_FUNC_INFO << "camera control is nil"; + return; + } + + connect(cameraControl, SIGNAL(captureModeChanged(QCamera::CaptureModes)), + SLOT(captureModeChanged(QCamera::CaptureModes))); + connect(cameraControl, SIGNAL(statusChanged(QCamera::Status)), + SLOT(cameraStatusChanged(QCamera::Status))); +} + +AVFMediaRecorderControlIOS::~AVFMediaRecorderControlIOS() +{ + [m_writer abort]; + + if (m_audioSettings) + [m_audioSettings release]; + if (m_videoSettings) + [m_videoSettings release]; +} + +QUrl AVFMediaRecorderControlIOS::outputLocation() const +{ + return m_outputLocation; +} + +bool AVFMediaRecorderControlIOS::setOutputLocation(const QUrl &location) +{ + m_outputLocation = location; + return location.scheme() == QLatin1String("file") || location.scheme().isEmpty(); +} + +QMediaRecorder::State AVFMediaRecorderControlIOS::state() const +{ + return m_state; +} + +QMediaRecorder::Status AVFMediaRecorderControlIOS::status() const +{ + return m_lastStatus; +} + +qint64 AVFMediaRecorderControlIOS::duration() const +{ + return m_writer.data().durationInMs; +} + +bool AVFMediaRecorderControlIOS::isMuted() const +{ + return false; +} + +qreal AVFMediaRecorderControlIOS::volume() const +{ + return 1.; +} + +void AVFMediaRecorderControlIOS::applySettings() +{ + AVFCameraSession *session = m_service->session(); + if (!session) + return; + + if (m_state != QMediaRecorder::StoppedState + || (session->state() != QCamera::ActiveState && session->state() != QCamera::LoadedState) + || !m_service->cameraControl()->captureMode().testFlag(QCamera::CaptureVideo)) { + return; + } + + // audio settings + m_audioSettings = m_service->audioEncoderSettingsControl()->applySettings(); + if (m_audioSettings) + [m_audioSettings retain]; + + // video settings + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; + m_videoSettings = m_service->videoEncoderSettingsControl()->applySettings(conn); + if (m_videoSettings) + [m_videoSettings retain]; +} + +void AVFMediaRecorderControlIOS::unapplySettings() +{ + m_service->audioEncoderSettingsControl()->unapplySettings(); + + AVCaptureConnection *conn = [m_service->videoOutput()->videoDataOutput() connectionWithMediaType:AVMediaTypeVideo]; + m_service->videoEncoderSettingsControl()->unapplySettings(conn); + + if (m_audioSettings) { + [m_audioSettings release]; + m_audioSettings = nil; + } + if (m_videoSettings) { + [m_videoSettings release]; + m_videoSettings = nil; + } +} + +void AVFMediaRecorderControlIOS::setState(QMediaRecorder::State state) +{ + Q_ASSERT(m_service->session() + && m_service->session()->captureSession()); + + if (!m_writer) { + qDebugCamera() << Q_FUNC_INFO << "Invalid recorder"; + return; + } + + if (state == m_state) + return; + + switch (state) { + case QMediaRecorder::RecordingState: + { + AVFCameraControl *cameraControl = m_service->cameraControl(); + Q_ASSERT(cameraControl); + + if (!(cameraControl->captureMode() & QCamera::CaptureVideo)) { + qDebugCamera() << Q_FUNC_INFO << "wrong capture mode, CaptureVideo expected"; + Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording")); + return; + } + + if (cameraControl->status() != QCamera::ActiveStatus) { + qDebugCamera() << Q_FUNC_INFO << "can not start record while camera is not active"; + Q_EMIT error(QMediaRecorder::ResourceError, tr("Failed to start recording")); + return; + } + + const QString path(m_outputLocation.scheme() == QLatin1String("file") ? + m_outputLocation.path() : m_outputLocation.toString()); + const QUrl fileURL(QUrl::fromLocalFile(m_storageLocation.generateFileName(path, QCamera::CaptureVideo, + QLatin1String("clip_"), + m_service->mediaContainerControl()->containerFormat()))); + + NSURL *nsFileURL = fileURL.toNSURL(); + if (!nsFileURL) { + qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL; + Q_EMIT error(QMediaRecorder::ResourceError, tr("Invalid output file URL")); + return; + } + if (!qt_is_writable_file_URL(nsFileURL)) { + qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL + << "(the location is not writable)"; + Q_EMIT error(QMediaRecorder::ResourceError, tr("Non-writeable file location")); + return; + } + if (qt_file_exists(nsFileURL)) { + // We test for/handle this error here since AWAssetWriter will raise an + // Objective-C exception, which is not good at all. + qWarning() << Q_FUNC_INFO << "invalid output URL:" << fileURL + << "(file already exists)"; + Q_EMIT error(QMediaRecorder::ResourceError, tr("File already exists")); + return; + } + + AVCaptureSession *session = m_service->session()->captureSession(); + // We stop session now so that no more frames for renderer's queue + // generated, will restart in assetWriterStarted. + [session stopRunning]; + + applySettings(); + + // Make sure the video is recorded in device orientation. + // The top of the video will match the side of the device which is on top + // when recording starts (regardless of the UI orientation). + AVFCameraInfo cameraInfo = m_service->session()->activeCameraInfo(); + int screenOrientation = 360 - m_orientationHandler.currentOrientation(); + float rotation = 0; + if (cameraInfo.position == QCamera::FrontFace) + rotation = (screenOrientation + cameraInfo.orientation) % 360; + else + rotation = (screenOrientation + (360 - cameraInfo.orientation)) % 360; + + if ([m_writer setupWithFileURL:nsFileURL + cameraService:m_service + audioSettings:m_audioSettings + videoSettings:m_videoSettings + transform:CGAffineTransformMakeRotation(qDegreesToRadians(rotation))]) { + + m_state = QMediaRecorder::RecordingState; + m_lastStatus = QMediaRecorder::StartingStatus; + + Q_EMIT actualLocationChanged(fileURL); + Q_EMIT stateChanged(m_state); + Q_EMIT statusChanged(m_lastStatus); + + // Apple recommends to call startRunning and do all + // setup on a special queue, and that's what we had + // initially (dispatch_async to writerQueue). Unfortunately, + // writer's queue is not the only queue/thread that can + // access/modify the session, and as a result we have + // all possible data/race-conditions with Obj-C exceptions + // at best and something worse in general. + // Now we try to only modify session on the same thread. + [m_writer start]; + } else { + [session startRunning]; + Q_EMIT error(QMediaRecorder::FormatError, tr("Failed to start recording")); + } + } break; + case QMediaRecorder::PausedState: + { + Q_EMIT error(QMediaRecorder::FormatError, tr("Recording pause not supported")); + return; + } break; + case QMediaRecorder::StoppedState: + { + // Do not check the camera status, we can stop if we started. + stopWriter(); + } + } +} + +void AVFMediaRecorderControlIOS::setMuted(bool muted) +{ + Q_UNUSED(muted); + qDebugCamera() << Q_FUNC_INFO << "not implemented"; +} + +void AVFMediaRecorderControlIOS::setVolume(qreal volume) +{ + Q_UNUSED(volume); + qDebugCamera() << Q_FUNC_INFO << "not implemented"; +} + +void AVFMediaRecorderControlIOS::assetWriterStarted() +{ + m_lastStatus = QMediaRecorder::RecordingStatus; + Q_EMIT statusChanged(QMediaRecorder::RecordingStatus); +} + +void AVFMediaRecorderControlIOS::assetWriterFinished() +{ + AVFCameraControl *cameraControl = m_service->cameraControl(); + Q_ASSERT(cameraControl); + + const QMediaRecorder::Status lastStatus = m_lastStatus; + const QMediaRecorder::State lastState = m_state; + if (cameraControl->captureMode() & QCamera::CaptureVideo) + m_lastStatus = QMediaRecorder::LoadedStatus; + else + m_lastStatus = QMediaRecorder::UnloadedStatus; + + unapplySettings(); + + m_service->videoOutput()->resetCaptureDelegate(); + [m_service->session()->captureSession() startRunning]; + m_state = QMediaRecorder::StoppedState; + if (m_lastStatus != lastStatus) + Q_EMIT statusChanged(m_lastStatus); + if (m_state != lastState) + Q_EMIT stateChanged(m_state); +} + +void AVFMediaRecorderControlIOS::captureModeChanged(QCamera::CaptureModes newMode) +{ + AVFCameraControl *cameraControl = m_service->cameraControl(); + Q_ASSERT(cameraControl); + + const QMediaRecorder::Status lastStatus = m_lastStatus; + + if (newMode & QCamera::CaptureVideo) { + if (cameraControl->status() == QCamera::ActiveStatus) + m_lastStatus = QMediaRecorder::LoadedStatus; + } else { + if (m_lastStatus == QMediaRecorder::RecordingStatus) + return stopWriter(); + else + m_lastStatus = QMediaRecorder::UnloadedStatus; + } + + if (m_lastStatus != lastStatus) + Q_EMIT statusChanged(m_lastStatus); +} + +void AVFMediaRecorderControlIOS::cameraStatusChanged(QCamera::Status newStatus) +{ + AVFCameraControl *cameraControl = m_service->cameraControl(); + Q_ASSERT(cameraControl); + + const QMediaRecorder::Status lastStatus = m_lastStatus; + const bool isCapture = cameraControl->captureMode() & QCamera::CaptureVideo; + if (newStatus == QCamera::StartingStatus) { + if (isCapture && m_lastStatus == QMediaRecorder::UnloadedStatus) + m_lastStatus = QMediaRecorder::LoadingStatus; + } else if (newStatus == QCamera::ActiveStatus) { + if (isCapture && m_lastStatus == QMediaRecorder::LoadingStatus) + m_lastStatus = QMediaRecorder::LoadedStatus; + } else { + if (m_lastStatus == QMediaRecorder::RecordingStatus) + return stopWriter(); + if (newStatus == QCamera::UnloadedStatus) + m_lastStatus = QMediaRecorder::UnloadedStatus; + } + + if (lastStatus != m_lastStatus) + Q_EMIT statusChanged(m_lastStatus); +} + +void AVFMediaRecorderControlIOS::stopWriter() +{ + if (m_lastStatus == QMediaRecorder::RecordingStatus) { + m_lastStatus = QMediaRecorder::FinalizingStatus; + + Q_EMIT statusChanged(m_lastStatus); + + [m_writer stop]; + } +} + +#include "moc_avfmediarecordercontrol_ios.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h new file mode 100644 index 000000000..178c75cad --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_ios_p.h @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 AVFMEDIARECORDERCONTROL_IOS_H +#define AVFMEDIARECORDERCONTROL_IOS_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 "avfmediaassetwriter.h" +#include "avfstoragelocation.h" +#include "avfcamerautility.h" + +#include <QtMultimedia/qmediarecordercontrol.h> +#include <private/qvideooutputorientationhandler_p.h> + +#include <QtCore/qglobal.h> +#include <QtCore/qurl.h> + +#include <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +class AVFCameraService; +class QString; +class QUrl; + +class AVFMediaRecorderControlIOS : public QMediaRecorderControl +{ + Q_OBJECT +public: + AVFMediaRecorderControlIOS(AVFCameraService *service, QObject *parent = nullptr); + ~AVFMediaRecorderControlIOS() override; + + 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; + void unapplySettings(); + +public Q_SLOTS: + void setState(QMediaRecorder::State state) override; + void setMuted(bool muted) override; + void setVolume(qreal volume) override; + +private: + + Q_INVOKABLE void assetWriterStarted(); + Q_INVOKABLE void assetWriterFinished(); + +private Q_SLOTS: + void captureModeChanged(QCamera::CaptureModes); + void cameraStatusChanged(QCamera::Status newStatus); + +private: + void stopWriter(); + + AVFCameraService *m_service; + AVFScopedPointer<QT_MANGLE_NAMESPACE(AVFMediaAssetWriter)> m_writer; + + QUrl m_outputLocation; + AVFStorageLocation m_storageLocation; + + QMediaRecorder::State m_state; + QMediaRecorder::Status m_lastStatus; + + NSDictionary *m_audioSettings; + NSDictionary *m_videoSettings; + QVideoOutputOrientationHandler m_orientationHandler; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIARECORDERCONTROL_IOS_H diff --git a/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h new file mode 100644 index 000000000..9d8f6566b --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediarecordercontrol_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 AVFMEDIARECORDERCONTROL_H +#define AVFMEDIARECORDERCONTROL_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/qurl.h> +#include <QtMultimedia/qmediarecordercontrol.h> + +#import <AVFoundation/AVFoundation.h> +#include "avfstoragelocation_p.h" +#include "avfcamerautility_p.h" + +@class AVFMediaRecorderDelegate; + +QT_BEGIN_NAMESPACE + +class AVFCameraSession; +class AVFCameraControl; +class AVFAudioInputSelectorControl; +class AVFCameraService; + +class AVFMediaRecorderControl : public QMediaRecorderControl +{ +Q_OBJECT +public: + AVFMediaRecorderControl(AVFCameraService *service, QObject *parent = nullptr); + ~AVFMediaRecorderControl(); + + 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; + void unapplySettings(); + +public Q_SLOTS: + void setState(QMediaRecorder::State state) override; + void setMuted(bool muted) override; + void setVolume(qreal volume) override; + + void handleRecordingStarted(); + void handleRecordingFinished(); + void handleRecordingFailed(const QString &message); + +private Q_SLOTS: + void setupSessionForCapture(); + void updateStatus(); + +private: + AVFCameraService *m_service; + AVFCameraControl *m_cameraControl; + AVFAudioInputSelectorControl *m_audioInputControl; + AVFCameraSession *m_session; + + bool m_connected; + QUrl m_outputLocation; + QMediaRecorder::State m_state; + QMediaRecorder::Status m_lastStatus; + + bool m_recordingStarted; + bool m_recordingFinished; + + bool m_muted; + qreal m_volume; + + AVCaptureDeviceInput *m_audioInput; + AVCaptureMovieFileOutput *m_movieOutput; + AVFMediaRecorderDelegate *m_recorderDelagate; + AVFStorageLocation m_storageLocation; + + AVFPSRange m_restoreFPS; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm new file mode 100644 index 000000000..c97ab1919 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol.mm @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediavideoprobecontrol_p.h" +#include <qvideoframe.h> + +QT_BEGIN_NAMESPACE + +AVFMediaVideoProbeControl::AVFMediaVideoProbeControl(QObject *parent) : + QMediaVideoProbeControl(parent) +{ +} + +AVFMediaVideoProbeControl::~AVFMediaVideoProbeControl() +{ + +} + +void AVFMediaVideoProbeControl::newFrameProbed(const QVideoFrame &frame) +{ + Q_EMIT videoFrameProbed(frame); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h new file mode 100644 index 000000000..c18bc4181 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfmediavideoprobecontrol_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 AVFMEDIAVIDEOPROBECONTROL_H +#define AVFMEDIAVIDEOPROBECONTROL_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 AVFMediaVideoProbeControl : public QMediaVideoProbeControl +{ + Q_OBJECT +public: + explicit AVFMediaVideoProbeControl(QObject *parent = nullptr); + ~AVFMediaVideoProbeControl(); + + void newFrameProbed(const QVideoFrame& frame); + +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAVIDEOPROBECONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm b/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm new file mode 100644 index 000000000..1855f8ec7 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfstoragelocation.mm @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfstoragelocation_p.h" +#include "QtCore/qstandardpaths.h" + + +QT_BEGIN_NAMESPACE + + +AVFStorageLocation::AVFStorageLocation() +{ +} + +AVFStorageLocation::~AVFStorageLocation() +{ +} + +/*! + * Generate the actual file name from user requested one. + * requestedName may be either empty (the default dir and naming theme is used), + * points to existing dir (the default name used) + * or specify the full actual path. + */ +QString AVFStorageLocation::generateFileName(const QString &requestedName, + QCamera::CaptureMode mode, + const QString &prefix, + const QString &ext) const +{ + if (requestedName.isEmpty()) + return generateFileName(prefix, defaultDir(mode), ext); + + if (QFileInfo(requestedName).isDir()) + return generateFileName(prefix, QDir(requestedName), ext); + + return requestedName; +} + +QDir AVFStorageLocation::defaultDir(QCamera::CaptureMode mode) const +{ + QStringList dirCandidates; + + if (mode == QCamera::CaptureVideo) { + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::MoviesLocation); + } else { + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + } + + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + for (const QString &path : qAsConst(dirCandidates)) { + if (QFileInfo(path).isWritable()) + return QDir(path); + } + + return QDir(); +} + +QString AVFStorageLocation::generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const +{ + QString lastClipKey = dir.absolutePath()+QLatin1Char(' ')+prefix+QLatin1Char(' ')+ext; + int lastClip = m_lastUsedIndex.value(lastClipKey, 0); + + if (lastClip == 0) { + //first run, find the maximum clip number during the fist capture + const auto list = dir.entryList(QStringList() << QString("%1*.%2").arg(prefix).arg(ext)); + for (const QString &fileName : list) { + int imgNumber = QStringView{fileName}.mid(prefix.length(), fileName.size()-prefix.length()-ext.length()-1).toInt(); + lastClip = qMax(lastClip, imgNumber); + } + } + + + //don't just rely on cached lastClip value, + //someone else may create a file after camera started + while (true) { + QString name = QString("%1%2.%3").arg(prefix) + .arg(lastClip+1, + 4, //fieldWidth + 10, + QLatin1Char('0')) + .arg(ext); + + QString path = dir.absoluteFilePath(name); + if (!QFileInfo(path).exists()) { + m_lastUsedIndex[lastClipKey] = lastClip+1; + return path; + } + + lastClip++; + } + + return QString(); +} + + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h b/src/multimedia/platform/avfoundation/camera/avfstoragelocation_p.h new file mode 100644 index 000000000..8794f0fae --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfstoragelocation_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 AVFSTORAGE_H +#define AVFSTORAGE_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 "qtmultimediaglobal.h" + +#include <QtCore/qdir.h> +#include <QtMultimedia/qcamera.h> + +QT_BEGIN_NAMESPACE + +class AVFStorageLocation +{ +public: + AVFStorageLocation(); + ~AVFStorageLocation(); + + QString generateFileName(const QString &requestedName, + QCamera::CaptureMode mode, + const QString &prefix, + const QString &ext) const; + + + QDir defaultDir(QCamera::CaptureMode mode) const; + QString generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const; + +private: + mutable QMap<QString, int> m_lastUsedIndex; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm new file mode 100644 index 000000000..0800e2021 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol.mm @@ -0,0 +1,385 @@ +/**************************************************************************** +** +** 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 "avfvideoencodersettingscontrol_p.h" + +#include "avfcameraservice_p.h" +#include "avfcamerautility_p.h" +#include "avfcamerasession_p.h" +#include "avfcamerarenderercontrol_p.h" + +#include <AVFoundation/AVFoundation.h> + +QT_BEGIN_NAMESPACE + +Q_GLOBAL_STATIC_WITH_ARGS(QStringList, supportedCodecs, (QStringList() << QLatin1String("avc1") + << QLatin1String("jpeg") + #ifdef Q_OS_OSX + << QLatin1String("ap4h") + << QLatin1String("apcn") + #endif + )) + +static bool format_supports_framerate(AVCaptureDeviceFormat *format, qreal fps) +{ + if (format && fps > qreal(0)) { + const qreal epsilon = 0.1; + for (AVFrameRateRange *range in format.videoSupportedFrameRateRanges) { + if (range.maxFrameRate - range.minFrameRate < epsilon) { + if (qAbs(fps - range.maxFrameRate) < epsilon) + return true; + } + + if (fps >= range.minFrameRate && fps <= range.maxFrameRate) + return true; + } + } + + return false; +} + +static bool real_list_contains(const QList<qreal> &list, qreal value) +{ + for (qreal r : list) { + if (qFuzzyCompare(r, value)) + return true; + } + return false; +} + +AVFVideoEncoderSettingsControl::AVFVideoEncoderSettingsControl(AVFCameraService *service) + : QVideoEncoderSettingsControl() + , m_service(service) + , m_restoreFormat(nil) +{ +} + +QList<QSize> AVFVideoEncoderSettingsControl::supportedResolutions(const QVideoEncoderSettings &settings, + bool *continuous) const +{ + Q_UNUSED(settings); + + if (continuous) + *continuous = true; + + // AVFoundation seems to support any resolution for recording, with the following limitations: + // - The recording resolution can't be higher than the camera's active resolution + // - On OS X, the recording resolution is automatically adjusted to have the same aspect ratio as + // the camera's active resolution + QList<QSize> resolutions; + resolutions.append(QSize(32, 32)); + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (device) { + int maximumWidth = 0; + const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(device, + m_service->session()->defaultCodec())); + for (int i = 0; i < formats.size(); ++i) { + const QSize res(qt_device_format_resolution(formats[i])); + if (res.width() > maximumWidth) + maximumWidth = res.width(); + } + + if (maximumWidth > 0) + resolutions.append(QSize(maximumWidth, maximumWidth)); + } + + if (resolutions.count() == 1) + resolutions.append(QSize(3840, 3840)); + + return resolutions; +} + +QList<qreal> AVFVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings, + bool *continuous) const +{ + QList<qreal> uniqueFrameRates; + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return uniqueFrameRates; + + if (continuous) + *continuous = false; + + QVector<AVFPSRange> allRates; + + if (!settings.resolution().isValid()) { + const QVector<AVCaptureDeviceFormat *> formats(qt_unique_device_formats(device, 0)); + for (int i = 0; i < formats.size(); ++i) { + AVCaptureDeviceFormat *format = formats.at(i); + allRates += qt_device_format_framerates(format); + } + } else { + AVCaptureDeviceFormat *format = qt_find_best_resolution_match(device, + settings.resolution(), + m_service->session()->defaultCodec()); + if (format) + allRates = qt_device_format_framerates(format); + } + + for (int j = 0; j < allRates.size(); ++j) { + if (!real_list_contains(uniqueFrameRates, allRates[j].first)) + uniqueFrameRates.append(allRates[j].first); + if (!real_list_contains(uniqueFrameRates, allRates[j].second)) + uniqueFrameRates.append(allRates[j].second); + } + + return uniqueFrameRates; +} + +QStringList AVFVideoEncoderSettingsControl::supportedVideoCodecs() const +{ + return *supportedCodecs; +} + +QString AVFVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("avc1")) + return QStringLiteral("H.264"); + else if (codecName == QLatin1String("jpeg")) + return QStringLiteral("M-JPEG"); +#ifdef Q_OS_OSX + else if (codecName == QLatin1String("ap4h")) + return QStringLiteral("Apple ProRes 4444"); + else if (codecName == QLatin1String("apcn")) + return QStringLiteral("Apple ProRes 422 Standard Definition"); +#endif + + return QString(); +} + +QVideoEncoderSettings AVFVideoEncoderSettingsControl::videoSettings() const +{ + return m_actualSettings; +} + +void AVFVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) +{ + if (m_requestedSettings == settings) + return; + + m_requestedSettings = m_actualSettings = settings; +} + +NSDictionary *AVFVideoEncoderSettingsControl::applySettings(AVCaptureConnection *connection) +{ + if (m_service->session()->state() != QCamera::LoadedState && + m_service->session()->state() != QCamera::ActiveState) { + return nil; + } + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return nil; + + AVFPSRange currentFps = qt_current_framerates(device, connection); + const bool needFpsChange = m_requestedSettings.frameRate() > 0 + && m_requestedSettings.frameRate() != currentFps.second; + + NSMutableDictionary *videoSettings = [NSMutableDictionary dictionary]; + + // -- Codec + + // AVVideoCodecKey is the only mandatory key + QString codec = m_requestedSettings.codec().isEmpty() ? supportedCodecs->first() : m_requestedSettings.codec(); + if (!supportedCodecs->contains(codec)) { + qWarning("Unsupported codec: '%s'", codec.toLocal8Bit().constData()); + codec = supportedCodecs->first(); + } + [videoSettings setObject:codec.toNSString() forKey:AVVideoCodecKey]; + m_actualSettings.setCodec(codec); + + // -- Resolution + + int w = m_requestedSettings.resolution().width(); + int h = m_requestedSettings.resolution().height(); + + if (AVCaptureDeviceFormat *currentFormat = device.activeFormat) { + CMFormatDescriptionRef formatDesc = currentFormat.formatDescription; + CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(formatDesc); + + // We have to change the device's activeFormat in 3 cases: + // - the requested recording resolution is higher than the current device resolution + // - the requested recording resolution has a different aspect ratio than the current device aspect ratio + // - the requested frame rate is not available for the current device format + AVCaptureDeviceFormat *newFormat = nil; + if ((w <= 0 || h <= 0) + && m_requestedSettings.frameRate() > 0 + && !format_supports_framerate(currentFormat, m_requestedSettings.frameRate())) { + + newFormat = qt_find_best_framerate_match(device, + m_service->session()->defaultCodec(), + m_requestedSettings.frameRate()); + + } else if (w > 0 && h > 0) { + AVCaptureDeviceFormat *f = qt_find_best_resolution_match(device, + m_requestedSettings.resolution(), + m_service->session()->defaultCodec()); + + if (f) { + CMVideoDimensions d = CMVideoFormatDescriptionGetDimensions(f.formatDescription); + qreal fAspectRatio = qreal(d.width) / d.height; + + if (w > dim.width || h > dim.height + || qAbs((qreal(dim.width) / dim.height) - fAspectRatio) > 0.01) { + newFormat = f; + } + } + } + + if (qt_set_active_format(device, newFormat, !needFpsChange)) { + m_restoreFormat = [currentFormat retain]; + formatDesc = newFormat.formatDescription; + dim = CMVideoFormatDescriptionGetDimensions(formatDesc); + } + + if (w > 0 && h > 0) { + // Make sure the recording resolution has the same aspect ratio as the device's + // current resolution + qreal deviceAspectRatio = qreal(dim.width) / dim.height; + qreal recAspectRatio = qreal(w) / h; + if (qAbs(deviceAspectRatio - recAspectRatio) > 0.01) { + if (recAspectRatio > deviceAspectRatio) + w = qRound(h * deviceAspectRatio); + else + h = qRound(w / deviceAspectRatio); + } + + // recording resolution can't be higher than the device's active resolution + w = qMin(w, dim.width); + h = qMin(h, dim.height); + } + } + + if (w > 0 && h > 0) { + // Width and height must be divisible by 2 + w += w & 1; + h += h & 1; + + [videoSettings setObject:[NSNumber numberWithInt:w] forKey:AVVideoWidthKey]; + [videoSettings setObject:[NSNumber numberWithInt:h] forKey:AVVideoHeightKey]; + m_actualSettings.setResolution(w, h); + } else { + m_actualSettings.setResolution(qt_device_format_resolution(device.activeFormat)); + } + + // -- FPS + + if (needFpsChange) { + m_restoreFps = currentFps; + const qreal fps = m_requestedSettings.frameRate(); + qt_set_framerate_limits(device, connection, fps, fps); + } + m_actualSettings.setFrameRate(qt_current_framerates(device, connection).second); + + // -- Codec Settings + + NSMutableDictionary *codecProperties = [NSMutableDictionary dictionary]; + int bitrate = -1; + float quality = -1.f; + + if (m_requestedSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + if (m_requestedSettings.quality() != QMultimedia::NormalQuality) { + if (codec != QLatin1String("jpeg")) { + qWarning("ConstantQualityEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); + } else { + switch (m_requestedSettings.quality()) { + case QMultimedia::VeryLowQuality: + quality = 0.f; + break; + case QMultimedia::LowQuality: + quality = 0.25f; + break; + case QMultimedia::HighQuality: + quality = 0.75f; + break; + case QMultimedia::VeryHighQuality: + quality = 1.f; + break; + default: + quality = -1.f; // NormalQuality, let the system decide + break; + } + } + } + } else if (m_requestedSettings.encodingMode() == QMultimedia::AverageBitRateEncoding){ + if (codec != QLatin1String("avc1")) + qWarning("AverageBitRateEncoding is not supported for codec: '%s'", codec.toLocal8Bit().constData()); + else + bitrate = m_requestedSettings.bitRate(); + } else { + qWarning("Encoding mode is not supported"); + } + + if (bitrate != -1) + [codecProperties setObject:[NSNumber numberWithInt:bitrate] forKey:AVVideoAverageBitRateKey]; + if (quality != -1.f) + [codecProperties setObject:[NSNumber numberWithFloat:quality] forKey:AVVideoQualityKey]; + + [videoSettings setObject:codecProperties forKey:AVVideoCompressionPropertiesKey]; + + return videoSettings; +} + +void AVFVideoEncoderSettingsControl::unapplySettings(AVCaptureConnection *connection) +{ + m_actualSettings = m_requestedSettings; + + AVCaptureDevice *device = m_service->session()->videoCaptureDevice(); + if (!device) + return; + + const bool needFpsChanged = m_restoreFps.first || m_restoreFps.second; + + if (m_restoreFormat) { + qt_set_active_format(device, m_restoreFormat, !needFpsChanged); + [m_restoreFormat release]; + m_restoreFormat = nil; + } + + if (needFpsChanged) { + qt_set_framerate_limits(device, connection, m_restoreFps.first, m_restoreFps.second); + m_restoreFps = AVFPSRange(); + } +} + +QT_END_NAMESPACE + +#include "moc_avfvideoencodersettingscontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h new file mode 100644 index 000000000..6d44d4a33 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/avfvideoencodersettingscontrol_p.h @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** 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 AVFVIDEOENCODERSETTINGSCONTROL_H +#define AVFVIDEOENCODERSETTINGSCONTROL_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> + +#include "avfcamerautility_p.h" +#import <AVFoundation/AVFoundation.h> + +@class NSDictionary; + +QT_BEGIN_NAMESPACE + +class AVFCameraService; + +class AVFVideoEncoderSettingsControl : public QVideoEncoderSettingsControl +{ + Q_OBJECT + +public: + explicit AVFVideoEncoderSettingsControl(AVFCameraService *service); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &requestedVideoSettings, + bool *continuous = nullptr) const override; + + QList<qreal> supportedFrameRates(const QVideoEncoderSettings &requestedVideoSettings, + bool *continuous = nullptr) const override; + + QStringList supportedVideoCodecs() const override; + QString videoCodecDescription(const QString &codecName) const override; + + QVideoEncoderSettings videoSettings() const override; + void setVideoSettings(const QVideoEncoderSettings &requestedVideoSettings) override; + + NSDictionary *applySettings(AVCaptureConnection *connection); + void unapplySettings(AVCaptureConnection *connection); + +private: + AVFCameraService *m_service; + + QVideoEncoderSettings m_requestedSettings; + QVideoEncoderSettings m_actualSettings; + + AVCaptureDeviceFormat *m_restoreFormat; + AVFPSRange m_restoreFps; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOENCODERSETTINGSCONTROL_H diff --git a/src/multimedia/platform/avfoundation/camera/camera.pri b/src/multimedia/platform/avfoundation/camera/camera.pri new file mode 100644 index 000000000..430094d14 --- /dev/null +++ b/src/multimedia/platform/avfoundation/camera/camera.pri @@ -0,0 +1,60 @@ +HEADERS += \ + $$PWD/avfcameradebug_p.h \ + $$PWD/avfcameraserviceplugin_p.h \ + $$PWD/avfcameracontrol_p.h \ + $$PWD/avfcamerametadatacontrol_p.h \ + $$PWD/avfimagecapturecontrol_p.h \ + $$PWD/avfcameraservice_p.h \ + $$PWD/avfcamerasession_p.h \ + $$PWD/avfstoragelocation_p.h \ + $$PWD/avfaudioinputselectorcontrol_p.h \ + $$PWD/avfmediavideoprobecontrol_p.h \ + $$PWD/avfcamerarenderercontrol_p.h \ + $$PWD/avfcameradevicecontrol_p.h \ + $$PWD/avfcamerafocuscontrol_p.h \ + $$PWD/avfcameraexposurecontrol_p.h \ + $$PWD/avfcamerautility_p.h \ + $$PWD/avfimageencodercontrol_p.h \ + $$PWD/avfvideoencodersettingscontrol_p.h \ + $$PWD/avfmediacontainercontrol_p.h \ + $$PWD/avfaudioencodersettingscontrol_p.h \ + $$PWD/avfcamerawindowcontrol_p.h \ + +SOURCES += \ + $$PWD/avfcameraserviceplugin.mm \ + $$PWD/avfcameracontrol.mm \ + $$PWD/avfcamerametadatacontrol.mm \ + $$PWD/avfimagecapturecontrol.mm \ + $$PWD/avfcameraservice.mm \ + $$PWD/avfcamerasession.mm \ + $$PWD/avfstoragelocation.mm \ + $$PWD/avfaudioinputselectorcontrol.mm \ + $$PWD/avfmediavideoprobecontrol.mm \ + $$PWD/avfcameradevicecontrol.mm \ + $$PWD/avfcamerarenderercontrol.mm \ + $$PWD/avfcamerafocuscontrol.mm \ + $$PWD/avfcameraexposurecontrol.mm \ + $$PWD/avfcamerautility.mm \ + $$PWD/avfimageencodercontrol.mm \ + $$PWD/avfvideoencodersettingscontrol.mm \ + $$PWD/avfmediacontainercontrol.mm \ + $$PWD/avfaudioencodersettingscontrol.mm \ + $$PWD/avfcamerawindowcontrol.mm \ + +osx { + +HEADERS += $$PWD/avfmediarecordercontrol_p.h +SOURCES += $$PWD/avfmediarecordercontrol.mm + +} + +ios { + +HEADERS += \ + $$PWD/avfmediaassetwriter_p.h \ + $$PWD/avfmediarecordercontrol_ios_p.h +SOURCES += \ + $$PWD/avfmediaassetwriter.mm \ + $$PWD/avfmediarecordercontrol_ios.mm + +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm new file mode 100644 index 000000000..64b625f0e --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink.mm @@ -0,0 +1,241 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfdisplaylink_p.h" +#include <QtCore/qcoreapplication.h> + +#ifdef QT_DEBUG_AVF +#include <QtCore/qdebug.h> +#endif + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#import <QuartzCore/CADisplayLink.h> +#import <Foundation/NSRunLoop.h> +#define _m_displayLink static_cast<DisplayLinkObserver*>(m_displayLink) +#else +#endif + +QT_USE_NAMESPACE + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +@interface DisplayLinkObserver : NSObject + +- (void)start; +- (void)stop; +- (void)displayLinkNotification:(CADisplayLink *)sender; + +@end + +@implementation DisplayLinkObserver +{ + AVFDisplayLink *m_avfDisplayLink; + CADisplayLink *m_displayLink; +} + +- (id)initWithAVFDisplayLink:(AVFDisplayLink *)link +{ + self = [super init]; + + if (self) { + m_avfDisplayLink = link; + m_displayLink = [[CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkNotification:)] retain]; + } + + return self; +} + +- (void) dealloc +{ + if (m_displayLink) { + [m_displayLink release]; + m_displayLink = nullptr; + } + + [super dealloc]; +} + +- (void)start +{ + [m_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; +} + +- (void)stop +{ + [m_displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; +} + +- (void)displayLinkNotification:(CADisplayLink *)sender +{ + Q_UNUSED(sender); + m_avfDisplayLink->displayLinkEvent(nullptr); +} + +@end +#else +static CVReturn CVDisplayLinkCallback(CVDisplayLinkRef displayLink, + const CVTimeStamp *inNow, + const CVTimeStamp *inOutputTime, + CVOptionFlags flagsIn, + CVOptionFlags *flagsOut, + void *displayLinkContext) +{ + Q_UNUSED(displayLink); + Q_UNUSED(inNow); + Q_UNUSED(flagsIn); + Q_UNUSED(flagsOut); + + AVFDisplayLink *link = (AVFDisplayLink *)displayLinkContext; + + link->displayLinkEvent(inOutputTime); + return kCVReturnSuccess; +} +#endif + +AVFDisplayLink::AVFDisplayLink(QObject *parent) + : QObject(parent) + , m_displayLink(nullptr) + , m_pendingDisplayLinkEvent(false) + , m_isActive(false) +{ +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + m_displayLink = [[DisplayLinkObserver alloc] initWithAVFDisplayLink:this]; +#else + // create display link for the main display + CVDisplayLinkCreateWithCGDisplay(kCGDirectMainDisplay, &m_displayLink); + if (m_displayLink) { + // set the current display of a display link. + CVDisplayLinkSetCurrentCGDisplay(m_displayLink, kCGDirectMainDisplay); + + // set the renderer output callback function + CVDisplayLinkSetOutputCallback(m_displayLink, &CVDisplayLinkCallback, this); + } +#endif +} + +AVFDisplayLink::~AVFDisplayLink() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + + if (m_displayLink) { + stop(); +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + [_m_displayLink release]; +#else + CVDisplayLinkRelease(m_displayLink); +#endif + m_displayLink = nullptr; + } +} + +bool AVFDisplayLink::isValid() const +{ + return m_displayLink != nullptr; +} + +bool AVFDisplayLink::isActive() const +{ + return m_isActive; +} + +void AVFDisplayLink::start() +{ + if (m_displayLink && !m_isActive) { +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + [_m_displayLink start]; +#else + CVDisplayLinkStart(m_displayLink); +#endif + m_isActive = true; + } +} + +void AVFDisplayLink::stop() +{ + if (m_displayLink && m_isActive) { +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + [_m_displayLink stop]; +#else + CVDisplayLinkStop(m_displayLink); +#endif + m_isActive = false; + } +} + +void AVFDisplayLink::displayLinkEvent(const CVTimeStamp *ts) +{ + // This function is called from a + // thread != gui thread. So we post the event. + // But we need to make sure that we don't post faster + // than the event loop can eat: + m_displayLinkMutex.lock(); + bool pending = m_pendingDisplayLinkEvent; + m_pendingDisplayLinkEvent = true; +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + Q_UNUSED(ts); + memset(&m_frameTimeStamp, 0, sizeof(CVTimeStamp)); +#else + m_frameTimeStamp = *ts; +#endif + m_displayLinkMutex.unlock(); + + if (!pending) + qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority); +} + +bool AVFDisplayLink::event(QEvent *event) +{ + switch (event->type()){ + case QEvent::User: { + m_displayLinkMutex.lock(); + m_pendingDisplayLinkEvent = false; + CVTimeStamp ts = m_frameTimeStamp; + m_displayLinkMutex.unlock(); + + Q_EMIT tick(ts); + + return false; + } + break; + default: + break; + } + return QObject::event(event); +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h new file mode 100644 index 000000000..6b95e1e07 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfdisplaylink_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** 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 AVFDISPLAYLINK_H +#define AVFDISPLAYLINK_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/qobject.h> +#include <QtCore/qmutex.h> + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#include <CoreVideo/CVBase.h> +#else +#include <QuartzCore/CVDisplayLink.h> +#endif + +QT_BEGIN_NAMESPACE + +class AVFDisplayLink : public QObject +{ + Q_OBJECT +public: + explicit AVFDisplayLink(QObject *parent = nullptr); + virtual ~AVFDisplayLink(); + bool isValid() const; + bool isActive() const; + +public Q_SLOTS: + void start(); + void stop(); + +Q_SIGNALS: + void tick(const CVTimeStamp &ts); + +public: + void displayLinkEvent(const CVTimeStamp *); + +protected: + virtual bool event(QEvent *) override; + +private: +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + void *m_displayLink; +#else + CVDisplayLinkRef m_displayLink; +#endif + QMutex m_displayLinkMutex; + bool m_pendingDisplayLinkEvent; + bool m_isActive; + CVTimeStamp m_frameTimeStamp; +}; + +QT_END_NAMESPACE + +#endif // AVFDISPLAYLINK_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm new file mode 100644 index 000000000..764c1edf8 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol.mm @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediaplayercontrol_p.h" +#include "avfmediaplayersession_p.h" + +QT_USE_NAMESPACE + +AVFMediaPlayerControl::AVFMediaPlayerControl(QObject *parent) : + QMediaPlayerControl(parent) +{ +} + +AVFMediaPlayerControl::~AVFMediaPlayerControl() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif +} + +void AVFMediaPlayerControl::setSession(AVFMediaPlayerSession *session) +{ + m_session = session; + + connect(m_session, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64))); + connect(m_session, SIGNAL(durationChanged(qint64)), this, SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(bufferStatusChanged(int)), this, SIGNAL(bufferStatusChanged(int))); + connect(m_session, SIGNAL(stateChanged(QMediaPlayer::State)), + this, SIGNAL(stateChanged(QMediaPlayer::State))); + connect(m_session, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), + this, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); + connect(m_session, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int))); + connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool))); + connect(m_session, SIGNAL(audioAvailableChanged(bool)), this, SIGNAL(audioAvailableChanged(bool))); + connect(m_session, SIGNAL(videoAvailableChanged(bool)), this, SIGNAL(videoAvailableChanged(bool))); + connect(m_session, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString))); + connect(m_session, &AVFMediaPlayerSession::playbackRateChanged, + this, &AVFMediaPlayerControl::playbackRateChanged); + connect(m_session, &AVFMediaPlayerSession::seekableChanged, + this, &AVFMediaPlayerControl::seekableChanged); +} + +QMediaPlayer::State AVFMediaPlayerControl::state() const +{ + return m_session->state(); +} + +QMediaPlayer::MediaStatus AVFMediaPlayerControl::mediaStatus() const +{ + return m_session->mediaStatus(); +} + +QUrl AVFMediaPlayerControl::media() const +{ + return m_session->media(); +} + +const QIODevice *AVFMediaPlayerControl::mediaStream() const +{ + return m_session->mediaStream(); +} + +void AVFMediaPlayerControl::setMedia(const QUrl &content, QIODevice *stream) +{ + const QUrl oldContent = m_session->media(); + + m_session->setMedia(content, stream); + + if (content != oldContent) + Q_EMIT mediaChanged(content); +} + +qint64 AVFMediaPlayerControl::position() const +{ + return m_session->position(); +} + +qint64 AVFMediaPlayerControl::duration() const +{ + return m_session->duration(); +} + +int AVFMediaPlayerControl::bufferStatus() const +{ + return m_session->bufferStatus(); +} + +int AVFMediaPlayerControl::volume() const +{ + return m_session->volume(); +} + +bool AVFMediaPlayerControl::isMuted() const +{ + return m_session->isMuted(); +} + +bool AVFMediaPlayerControl::isAudioAvailable() const +{ + return m_session->isAudioAvailable(); +} + +bool AVFMediaPlayerControl::isVideoAvailable() const +{ + return m_session->isVideoAvailable(); +} + +bool AVFMediaPlayerControl::isSeekable() const +{ + return m_session->isSeekable(); +} + +QMediaTimeRange AVFMediaPlayerControl::availablePlaybackRanges() const +{ + return m_session->availablePlaybackRanges(); +} + +qreal AVFMediaPlayerControl::playbackRate() const +{ + return m_session->playbackRate(); +} + +void AVFMediaPlayerControl::setPlaybackRate(qreal rate) +{ + m_session->setPlaybackRate(rate); +} + +void AVFMediaPlayerControl::setPosition(qint64 pos) +{ + m_session->setPosition(pos); +} + +void AVFMediaPlayerControl::play() +{ + m_session->play(); +} + +void AVFMediaPlayerControl::pause() +{ + m_session->pause(); +} + +void AVFMediaPlayerControl::stop() +{ + m_session->stop(); +} + +void AVFMediaPlayerControl::setVolume(int volume) +{ + m_session->setVolume(volume); +} + +void AVFMediaPlayerControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h new file mode 100644 index 000000000..50a75427c --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayercontrol_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** 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 AVFMEDIAPLAYERCONTROL_H +#define AVFMEDIAPLAYERCONTROL_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 <QtMultimedia/QMediaPlayerControl> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + +class AVFMediaPlayerSession; + +class AVFMediaPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT +public: + explicit AVFMediaPlayerControl(QObject *parent = nullptr); + ~AVFMediaPlayerControl(); + + void setSession(AVFMediaPlayerSession *session); + + QMediaPlayer::State state() const override; + QMediaPlayer::MediaStatus mediaStatus() const override; + + QUrl media() const override; + const QIODevice *mediaStream() const override; + void setMedia(const QUrl &content, QIODevice *stream) override; + + qint64 position() const override; + qint64 duration() const override; + + int bufferStatus() const override; + + int volume() const override; + bool isMuted() 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; + +public Q_SLOTS: + void setPosition(qint64 pos) override; + + void play() override; + void pause() override; + void stop() override; + + void setVolume(int volume) override; + void setMuted(bool muted) override; + +private: + AVFMediaPlayerSession *m_session; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAPLAYERCONTROL_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm new file mode 100644 index 000000000..5a5780e52 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol.mm @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediaplayermetadatacontrol_p.h" +#include "avfmediaplayersession_p.h" + +#include <QtMultimedia/qmediametadata.h> + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +AVFMediaPlayerMetaDataControl::AVFMediaPlayerMetaDataControl(AVFMediaPlayerSession *session, QObject *parent) + : QMetaDataReaderControl(parent) + , m_session(session) + , m_asset(nullptr) +{ + QObject::connect(m_session, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), this, SLOT(updateTags())); +} + +AVFMediaPlayerMetaDataControl::~AVFMediaPlayerMetaDataControl() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif +} + +bool AVFMediaPlayerMetaDataControl::isMetaDataAvailable() const +{ + return !m_tags.isEmpty(); +} + +bool AVFMediaPlayerMetaDataControl::isWritable() const +{ + return false; +} + +QVariant AVFMediaPlayerMetaDataControl::metaData(const QString &key) const +{ + return m_tags.value(key); +} + +QStringList AVFMediaPlayerMetaDataControl::availableMetaData() const +{ + return m_tags.keys(); +} + +static QString itemKey(AVMetadataItem *item) +{ + NSString *keyString = [item commonKey]; + + if (keyString.length != 0) { + if ([keyString isEqualToString:AVMetadataCommonKeyTitle]) { + return QMediaMetaData::Title; + } else if ([keyString isEqualToString: AVMetadataCommonKeySubject]) { + return QMediaMetaData::SubTitle; + } else if ([keyString isEqualToString: AVMetadataCommonKeyDescription]) { + return QMediaMetaData::Description; + } else if ([keyString isEqualToString: AVMetadataCommonKeyPublisher]) { + return QMediaMetaData::Publisher; + } else if ([keyString isEqualToString: AVMetadataCommonKeyCreationDate]) { + return QMediaMetaData::Date; + } else if ([keyString isEqualToString: AVMetadataCommonKeyType]) { + return QMediaMetaData::MediaType; + } else if ([keyString isEqualToString: AVMetadataCommonKeyLanguage]) { + return QMediaMetaData::Language; + } else if ([keyString isEqualToString: AVMetadataCommonKeyCopyrights]) { + return QMediaMetaData::Copyright; + } else if ([keyString isEqualToString: AVMetadataCommonKeyAlbumName]) { + return QMediaMetaData::AlbumTitle; + } else if ([keyString isEqualToString: AVMetadataCommonKeyAuthor]) { + return QMediaMetaData::Author; + } else if ([keyString isEqualToString: AVMetadataCommonKeyArtist]) { + return QMediaMetaData::ContributingArtist; + } else if ([keyString isEqualToString: AVMetadataCommonKeyArtwork]) { + return QMediaMetaData::PosterUrl; + } + } + + return QString(); +} + +void AVFMediaPlayerMetaDataControl::updateTags() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVAsset *currentAsset = static_cast<AVAsset*>(m_session->currentAssetHandle()); + + //Don't read the tags from the same asset more than once + if (currentAsset == m_asset) + return; + + m_asset = currentAsset; + + QVariantMap oldTags = m_tags; + //Since we've changed assets, clear old tags + m_tags.clear(); + bool changed = false; + + // TODO: also process ID3, iTunes and QuickTime metadata + + NSArray *metadataItems = [currentAsset commonMetadata]; + for (AVMetadataItem* item in metadataItems) { + const QString key = itemKey(item); + if (!key.isEmpty()) { + const QString value = QString::fromNSString([item stringValue]); + if (!value.isNull()) { + m_tags.insert(key, value); + if (value != oldTags.value(key)) { + changed = true; + Q_EMIT metaDataChanged(key, value); + } + } + } + } + + if (oldTags.isEmpty() != m_tags.isEmpty()) { + Q_EMIT metaDataAvailableChanged(!m_tags.isEmpty()); + changed = true; + } + + if (changed) + Q_EMIT metaDataChanged(); +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_p.h new file mode 100644 index 000000000..1f8ecf26f --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayermetadatacontrol_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 AVFMEDIAPLAYERMETADATACONTROL_H +#define AVFMEDIAPLAYERMETADATACONTROL_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 <QtMultimedia/QMetaDataReaderControl> +#include <QtCore/qvariant.h> + +QT_BEGIN_NAMESPACE + +class AVFMediaPlayerSession; + +class AVFMediaPlayerMetaDataControl : public QMetaDataReaderControl +{ + Q_OBJECT +public: + explicit AVFMediaPlayerMetaDataControl(AVFMediaPlayerSession *session, QObject *parent = nullptr); + virtual ~AVFMediaPlayerMetaDataControl(); + + bool isMetaDataAvailable() const override; + bool isWritable() const; + + QVariant metaData(const QString &key) const override; + QStringList availableMetaData() const override; + +private Q_SLOTS: + void updateTags(); + +private: + AVFMediaPlayerSession *m_session; + QVariantMap m_tags; + void *m_asset; + +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAPLAYERMETADATACONTROL_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm new file mode 100644 index 000000000..b8d3d4e82 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice.mm @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediaplayerservice_p.h" +#include "avfmediaplayersession_p.h" +#include "avfmediaplayercontrol_p.h" +#include "avfmediaplayermetadatacontrol_p.h" +#include "avfvideooutput_p.h" +#if QT_CONFIG(opengl) +#include "avfvideorenderercontrol_p.h" +#endif +#include "avfvideowindowcontrol_p.h" + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +AVFMediaPlayerService::AVFMediaPlayerService(QObject *parent) + : QMediaService(parent) + , m_videoOutput(nullptr) +{ + m_session = new AVFMediaPlayerSession(this); + m_control = new AVFMediaPlayerControl(this); + m_control->setSession(m_session); + m_playerMetaDataControl = new AVFMediaPlayerMetaDataControl(m_session, this); + + connect(m_control, SIGNAL(mediaChanged(QMediaContent)), m_playerMetaDataControl, SLOT(updateTags())); +} + +AVFMediaPlayerService::~AVFMediaPlayerService() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + delete m_session; +} + +QObject *AVFMediaPlayerService::requestControl(const char *name) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << name; +#endif + + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) + return m_control; + + if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) + return m_playerMetaDataControl; + +#if QT_CONFIG(opengl) + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (!m_videoOutput) + m_videoOutput = new AVFVideoRendererControl(this); + + m_session->setVideoOutput(qobject_cast<AVFVideoOutput*>(m_videoOutput)); + return m_videoOutput; + } +#endif + if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + if (!m_videoOutput) + m_videoOutput = new AVFVideoWindowControl(this); + + m_session->setVideoOutput(qobject_cast<AVFVideoOutput*>(m_videoOutput)); + return m_videoOutput; + } + return nullptr; +} + +void AVFMediaPlayerService::releaseControl(QObject *control) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << control; +#endif + if (m_videoOutput == control) { +#if QT_CONFIG(opengl) + AVFVideoRendererControl *renderControl = qobject_cast<AVFVideoRendererControl*>(m_videoOutput); + + if (renderControl) + renderControl->setSurface(nullptr); +#endif + m_videoOutput = nullptr; + m_session->setVideoOutput(nullptr); + + delete control; + } +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_p.h new file mode 100644 index 000000000..8d5f63da2 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerservice_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 AVFMEDIAPLAYERSERVICE_H +#define AVFMEDIAPLAYERSERVICE_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 <QtMultimedia/QMediaService> + +QT_BEGIN_NAMESPACE + +class AVFMediaPlayerSession; +class AVFMediaPlayerControl; +class AVFMediaPlayerMetaDataControl; +class AVFVideoOutput; + +class AVFMediaPlayerService : public QMediaService +{ +public: + explicit AVFMediaPlayerService(QObject *parent = nullptr); + ~AVFMediaPlayerService(); + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *control) override; + +private: + AVFMediaPlayerSession *m_session; + AVFMediaPlayerControl *m_control; + QObject *m_videoOutput; + AVFMediaPlayerMetaDataControl *m_playerMetaDataControl; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAPLAYERSERVICE_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm new file mode 100644 index 000000000..504909598 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin.mm @@ -0,0 +1,99 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediaplayerserviceplugin_p.h" +#include <QtCore/QDebug> + +#include "avfmediaplayerservice_p.h" + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +AVFMediaPlayerServicePlugin::AVFMediaPlayerServicePlugin() +{ + buildSupportedTypes(); +} + +QMediaService *AVFMediaPlayerServicePlugin::create(const QString &key) +{ +#ifdef QT_DEBUG_AVF + qDebug() << "AVFMediaPlayerServicePlugin::create" << key; +#endif + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new AVFMediaPlayerService; + + qWarning() << "unsupported key: " << key; + return nullptr; +} + +void AVFMediaPlayerServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QMultimedia::SupportEstimate AVFMediaPlayerServicePlugin::hasSupport(const QString &mimeType, const QStringList &codecs) const +{ + Q_UNUSED(codecs); + + if (m_supportedMimeTypes.contains(mimeType)) + return QMultimedia::ProbablySupported; + + return QMultimedia::MaybeSupported; +} + +QStringList AVFMediaPlayerServicePlugin::supportedMimeTypes() const +{ + return m_supportedMimeTypes; +} + +void AVFMediaPlayerServicePlugin::buildSupportedTypes() +{ + //Populate m_supportedMimeTypes with mimetypes AVAsset supports + NSArray *mimeTypes = [AVURLAsset audiovisualMIMETypes]; + for (NSString *mimeType in mimeTypes) + { + m_supportedMimeTypes.append(QString::fromUtf8([mimeType UTF8String])); + } +#ifdef QT_DEBUG_AVF + qDebug() << "AVFMediaPlayerServicePlugin::buildSupportedTypes"; + qDebug() << "Supported Types: " << m_supportedMimeTypes; +#endif + +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_p.h new file mode 100644 index 000000000..d6e17c446 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayerserviceplugin_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 AVFMEDIAPLAYERSERVICEPLUGIN_H +#define AVFMEDIAPLAYERSERVICEPLUGIN_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/qglobal.h> +#include <QtMultimedia/qmediaserviceproviderplugin.h> + +QT_BEGIN_NAMESPACE + +class AVFMediaPlayerServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedFormatsInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedFormatsInterface) + +public: + explicit AVFMediaPlayerServicePlugin(); + + QMediaService* create(QString const& key) override; + void release(QMediaService *service) override; + + QMultimedia::SupportEstimate hasSupport(const QString &mimeType, const QStringList& codecs) const override; + QStringList supportedMimeTypes() const override; + +private: + void buildSupportedTypes(); + + QStringList m_supportedMimeTypes; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAPLAYERSERVICEPLUGIN_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm new file mode 100644 index 000000000..d6824a3dc --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession.mm @@ -0,0 +1,1067 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfmediaplayersession_p.h" +#include "avfmediaplayerservice_p.h" +#include "avfvideooutput_p.h" + +#include <qpointer.h> +#include <QFileInfo> + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +//AVAsset Keys +static NSString* const AVF_TRACKS_KEY = @"tracks"; +static NSString* const AVF_PLAYABLE_KEY = @"playable"; + +//AVPlayerItem keys +static NSString* const AVF_STATUS_KEY = @"status"; +static NSString* const AVF_BUFFER_LIKELY_KEEP_UP_KEY = @"playbackLikelyToKeepUp"; + +//AVPlayer keys +static NSString* const AVF_RATE_KEY = @"rate"; +static NSString* const AVF_CURRENT_ITEM_KEY = @"currentItem"; +static NSString* const AVF_CURRENT_ITEM_DURATION_KEY = @"currentItem.duration"; + +static void *AVFMediaPlayerSessionObserverRateObservationContext = &AVFMediaPlayerSessionObserverRateObservationContext; +static void *AVFMediaPlayerSessionObserverStatusObservationContext = &AVFMediaPlayerSessionObserverStatusObservationContext; +static void *AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext = &AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext; +static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMediaPlayerSessionObserverCurrentItemObservationContext; +static void *AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext = &AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext; + +@interface AVFMediaPlayerSessionObserver : NSObject<AVAssetResourceLoaderDelegate> + +@property (readonly, getter=player) AVPlayer* m_player; +@property (readonly, getter=playerItem) AVPlayerItem* m_playerItem; +@property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer; +@property (readonly, getter=session) AVFMediaPlayerSession* m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session; +- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType; +- (void) unloadMedia; +- (void) prepareToPlayAsset:(AVURLAsset *)asset withKeys:(NSArray *)requestedKeys; +- (void) assetFailedToPrepareForPlayback:(NSError *)error; +- (void) playerItemDidReachEnd:(NSNotification *)notification; +- (void) playerItemTimeJumped:(NSNotification *)notification; +- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object + change:(NSDictionary *)change context:(void *)context; +- (void) detatchSession; +- (void) dealloc; +- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest; +@end + +@implementation AVFMediaPlayerSessionObserver +{ +@private + AVFMediaPlayerSession *m_session; + AVPlayer *m_player; + AVPlayerItem *m_playerItem; + AVPlayerLayer *m_playerLayer; + NSURL *m_URL; + BOOL m_bufferIsLikelyToKeepUp; + NSData *m_data; + NSString *m_mimeType; +} + +@synthesize m_player, m_playerItem, m_playerLayer, m_session; + +- (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session +{ + if (!(self = [super init])) + return nil; + + self->m_session = session; + self->m_bufferIsLikelyToKeepUp = FALSE; + return self; +} + +- (void) setURL:(NSURL *)url mimeType:(NSString *)mimeType +{ + [m_mimeType release]; + m_mimeType = [mimeType retain]; + + if (m_URL != url) + { + [m_URL release]; + m_URL = [url copy]; + + //Create an asset for inspection of a resource referenced by a given URL. + //Load the values for the asset keys "tracks", "playable". + + // use __block to avoid maintaining strong references on variables captured by the + // following block callback + __block AVURLAsset *asset = [[AVURLAsset URLAssetWithURL:m_URL options:nil] retain]; + [asset.resourceLoader setDelegate:self queue:dispatch_get_main_queue()]; + + __block NSArray *requestedKeys = [[NSArray arrayWithObjects:AVF_TRACKS_KEY, AVF_PLAYABLE_KEY, nil] retain]; + + __block AVFMediaPlayerSessionObserver *blockSelf = self; + QPointer<AVFMediaPlayerSession> session(m_session); + + // Tells the asset to load the values of any of the specified keys that are not already loaded. + [asset loadValuesAsynchronouslyForKeys:requestedKeys completionHandler: + ^{ + dispatch_async( dispatch_get_main_queue(), + ^{ + if (session) + [blockSelf prepareToPlayAsset:asset withKeys:requestedKeys]; + [asset release]; + [requestedKeys release]; + }); + }]; + } +} + +- (void) unloadMedia +{ + if (m_playerItem) { + [m_playerItem removeObserver:self forKeyPath:AVF_STATUS_KEY]; + [m_playerItem removeObserver:self forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + m_playerItem = 0; + } + if (m_player) { + [m_player setRate:0.0]; + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY]; + [m_player removeObserver:self forKeyPath:AVF_CURRENT_ITEM_KEY]; + [m_player removeObserver:self forKeyPath:AVF_RATE_KEY]; + [m_player release]; + m_player = 0; + } + if (m_playerLayer) { + [m_playerLayer release]; + m_playerLayer = 0; + } +#if defined(Q_OS_IOS) + [[AVAudioSession sharedInstance] setActive:NO error:nil]; +#endif +} + +- (void) prepareToPlayAsset:(AVURLAsset *)asset + withKeys:(NSArray *)requestedKeys +{ + //Make sure that the value of each key has loaded successfully. + for (NSString *thisKey in requestedKeys) + { + NSError *error = nil; + AVKeyValueStatus keyStatus = [asset statusOfValueForKey:thisKey error:&error]; +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << [thisKey UTF8String] << " status: " << keyStatus; +#endif + if (keyStatus == AVKeyValueStatusFailed) + { + [self assetFailedToPrepareForPlayback:error]; + return; + } + } + + //Use the AVAsset playable property to detect whether the asset can be played. +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "isPlayable: " << [asset isPlayable]; +#endif + if (!asset.playable) + { + //Generate an error describing the failure. + NSString *localizedDescription = NSLocalizedString(@"Item cannot be played", @"Item cannot be played description"); + NSString *localizedFailureReason = NSLocalizedString(@"The assets tracks were loaded, but could not be made playable.", @"Item cannot be played failure reason"); + NSDictionary *errorDict = [NSDictionary dictionaryWithObjectsAndKeys: + localizedDescription, NSLocalizedDescriptionKey, + localizedFailureReason, NSLocalizedFailureReasonErrorKey, + nil]; + NSError *assetCannotBePlayedError = [NSError errorWithDomain:@"StitchedStreamPlayer" code:0 userInfo:errorDict]; + + [self assetFailedToPrepareForPlayback:assetCannotBePlayedError]; + + return; + } + + //At this point we're ready to set up for playback of the asset. + //Stop observing our prior AVPlayerItem, if we have one. + if (m_playerItem) + { + //Remove existing player item key value observers and notifications. + [self unloadMedia]; + } + + //Create a new instance of AVPlayerItem from the now successfully loaded AVAsset. + m_playerItem = [AVPlayerItem playerItemWithAsset:asset]; + + //Observe the player item "status" key to determine when it is ready to play. + [m_playerItem addObserver:self + forKeyPath:AVF_STATUS_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverStatusObservationContext]; + + [m_playerItem addObserver:self + forKeyPath:AVF_BUFFER_LIKELY_KEEP_UP_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext]; + + //When the player item has played to its end time we'll toggle + //the movie controller Pause button to be the Play button + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemDidReachEnd:) + name:AVPlayerItemDidPlayToEndTimeNotification + object:m_playerItem]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(playerItemTimeJumped:) + name:AVPlayerItemTimeJumpedNotification + object:m_playerItem]; + + //Get a new AVPlayer initialized to play the specified player item. + m_player = [AVPlayer playerWithPlayerItem:m_playerItem]; + [m_player retain]; + + //Set the initial volume on new player object + if (self.session) { + [m_player setVolume:m_session->volume() / 100.0f]; + [m_player setMuted:m_session->isMuted()]; + } + + //Create a new player layer if we don't have one already + if (!m_playerLayer) + { + m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:m_player]; + [m_playerLayer retain]; + m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); + } + + //Observe the AVPlayer "currentItem" property to find out when any + //AVPlayer replaceCurrentItemWithPlayerItem: replacement will/did + //occur. + [m_player addObserver:self + forKeyPath:AVF_CURRENT_ITEM_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverCurrentItemObservationContext]; + + //Observe the AVPlayer "rate" property to update the scrubber control. + [m_player addObserver:self + forKeyPath:AVF_RATE_KEY + options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew + context:AVFMediaPlayerSessionObserverRateObservationContext]; + + //Observe the duration for getting the buffer state + [m_player addObserver:self + forKeyPath:AVF_CURRENT_ITEM_DURATION_KEY + options:0 + context:AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext]; +#if defined(Q_OS_IOS) + [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil]; + [[AVAudioSession sharedInstance] setActive:YES error:nil]; +#endif +} + +-(void) assetFailedToPrepareForPlayback:(NSError *)error +{ + Q_UNUSED(error); + QMetaObject::invokeMethod(m_session, "processMediaLoadError", Qt::AutoConnection); +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; + qDebug() << [[error localizedDescription] UTF8String]; + qDebug() << [[error localizedFailureReason] UTF8String]; + qDebug() << [[error localizedRecoverySuggestion] UTF8String]; +#endif +} + +- (void) playerItemDidReachEnd:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (self.session) + QMetaObject::invokeMethod(m_session, "processEOS", Qt::AutoConnection); +} + +- (void) playerItemTimeJumped:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (self.session) + QMetaObject::invokeMethod(m_session, "processPositionChange", Qt::AutoConnection); +} + +- (void) observeValueForKeyPath:(NSString*) path + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + //AVPlayerItem "status" property value observer. + if (context == AVFMediaPlayerSessionObserverStatusObservationContext) + { + AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue]; + switch (status) + { + //Indicates that the status of the player is not yet known because + //it has not tried to load new media resources for playback + case AVPlayerStatusUnknown: + { + //QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusReadyToPlay: + { + //Once the AVPlayerItem becomes ready to play, i.e. + //[playerItem status] == AVPlayerItemStatusReadyToPlay, + //its duration can be fetched from the item. + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); + } + break; + + case AVPlayerStatusFailed: + { + AVPlayerItem *playerItem = static_cast<AVPlayerItem*>(object); + [self assetFailedToPrepareForPlayback:playerItem.error]; + + if (self.session) + QMetaObject::invokeMethod(m_session, "processLoadStateFailure", Qt::AutoConnection); + } + break; + } + } + else if (context == AVFMediaPlayerSessionObserverBufferLikelyToKeepUpContext) + { + const bool isPlaybackLikelyToKeepUp = [m_playerItem isPlaybackLikelyToKeepUp]; + if (isPlaybackLikelyToKeepUp != m_bufferIsLikelyToKeepUp) { + m_bufferIsLikelyToKeepUp = isPlaybackLikelyToKeepUp; + QMetaObject::invokeMethod(m_session, "processBufferStateChange", Qt::AutoConnection, + Q_ARG(int, isPlaybackLikelyToKeepUp ? 100 : 0)); + } + } + //AVPlayer "rate" property value observer. + else if (context == AVFMediaPlayerSessionObserverRateObservationContext) + { + //QMetaObject::invokeMethod(m_session, "setPlaybackRate", Qt::AutoConnection, Q_ARG(qreal, [m_player rate])); + } + //AVPlayer "currentItem" property observer. + //Called when the AVPlayer replaceCurrentItemWithPlayerItem: + //replacement will/did occur. + else if (context == AVFMediaPlayerSessionObserverCurrentItemObservationContext) + { + AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey]; + if (m_playerItem != newPlayerItem) + m_playerItem = newPlayerItem; + } + else if (context == AVFMediaPlayerSessionObserverCurrentItemDurationObservationContext) + { + const CMTime time = [m_playerItem duration]; + const qint64 duration = static_cast<qint64>(float(time.value) / float(time.timescale) * 1000.0f); + if (self.session) + QMetaObject::invokeMethod(m_session, "processDurationChange", Qt::AutoConnection, Q_ARG(qint64, duration)); + } + else + { + [super observeValueForKeyPath:path ofObject:object change:change context:context]; + } +} + +- (void) detatchSession +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + m_session = 0; +} + +- (void) dealloc +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + [self unloadMedia]; + + if (m_URL) { + [m_URL release]; + } + + [m_mimeType release]; + [super dealloc]; +} + +- (BOOL) resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest +{ + Q_UNUSED(resourceLoader); + + if (![loadingRequest.request.URL.scheme isEqualToString:@"iodevice"]) + return NO; + + QIODevice *device = m_session->mediaStream(); + if (!device) + return NO; + + device->seek(loadingRequest.dataRequest.requestedOffset); + if (loadingRequest.contentInformationRequest) { + loadingRequest.contentInformationRequest.contentType = m_mimeType; + loadingRequest.contentInformationRequest.contentLength = device->size(); + loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES; + } + + if (loadingRequest.dataRequest) { + NSInteger requestedLength = loadingRequest.dataRequest.requestedLength; + int maxBytes = qMin(32 * 1064, int(requestedLength)); + char buffer[maxBytes]; + NSInteger submitted = 0; + while (submitted < requestedLength) { + qint64 len = device->read(buffer, maxBytes); + if (len < 1) + break; + + [loadingRequest.dataRequest respondWithData:[NSData dataWithBytes:buffer length:len]]; + submitted += len; + } + + // Finish loading even if not all bytes submitted. + [loadingRequest finishLoading]; + } + + return YES; +} +@end + +AVFMediaPlayerSession::AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent) + : QObject(parent) + , m_service(service) + , m_videoOutput(nullptr) + , m_state(QMediaPlayer::StoppedState) + , m_mediaStatus(QMediaPlayer::NoMedia) + , m_mediaStream(nullptr) + , m_muted(false) + , m_tryingAsync(false) + , m_volume(100) + , m_rate(1.0) + , m_requestedPosition(-1) + , m_duration(0) + , m_bufferStatus(0) + , m_videoAvailable(false) + , m_audioAvailable(false) + , m_seekable(false) +{ + m_observer = [[AVFMediaPlayerSessionObserver alloc] initWithMediaPlayerSession:this]; +} + +AVFMediaPlayerSession::~AVFMediaPlayerSession() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + //Detatch the session from the sessionObserver (which could still be alive trying to communicate with this session). + [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) detatchSession]; + [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) release]; +} + +void AVFMediaPlayerSession::setVideoOutput(AVFVideoOutput *output) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << output; +#endif + + if (m_videoOutput == output) + return; + + //Set the current output layer to null to stop rendering + if (m_videoOutput) { + m_videoOutput->setLayer(nullptr); + } + + m_videoOutput = output; + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) + m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]); +} + +void *AVFMediaPlayerSession::currentAssetHandle() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + AVAsset *currentAsset = [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem] asset]; + return currentAsset; +} + +QMediaPlayer::State AVFMediaPlayerSession::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus AVFMediaPlayerSession::mediaStatus() const +{ + return m_mediaStatus; +} + +QUrl AVFMediaPlayerSession::media() const +{ + return m_resources; +} + +QIODevice *AVFMediaPlayerSession::mediaStream() const +{ + return m_mediaStream; +} + +static void setURL(void *observer, const QByteArray &url, const QString &mimeType = QString()) +{ + NSString *urlString = [NSString stringWithUTF8String:url.constData()]; + NSURL *nsurl = [NSURL URLWithString:urlString]; + [static_cast<AVFMediaPlayerSessionObserver*>(observer) setURL:nsurl mimeType:[NSString stringWithUTF8String:mimeType.toLatin1().constData()]]; +} + +static void setStreamURL(void *observer, const QByteArray &url) +{ + setURL(observer, QByteArrayLiteral("iodevice://") + url, QFileInfo(url).suffix()); +} + +void AVFMediaPlayerSession::setMedia(const QUrl &content, QIODevice *stream) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << content.request().url(); +#endif + + [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) unloadMedia]; + + m_resources = content; + resetStream(stream); + + setAudioAvailable(false); + setVideoAvailable(false); + setSeekable(false); + m_requestedPosition = -1; + Q_EMIT positionChanged(position()); + + const QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus; + const QMediaPlayer::State oldState = m_state; + + if (!m_mediaStream && content.isEmpty()) { + m_mediaStatus = QMediaPlayer::NoMedia; + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + + m_state = QMediaPlayer::StoppedState; + if (m_state != oldState) + Q_EMIT stateChanged(m_state); + + return; + } + + m_mediaStatus = QMediaPlayer::LoadingMedia; + if (m_mediaStatus != oldMediaStatus) + Q_EMIT mediaStatusChanged(m_mediaStatus); + + if (m_mediaStream) { + // If there is a data, try to load it, + // otherwise wait for readyRead. + if (m_mediaStream->size()) + setStreamURL(m_observer, m_resources.toEncoded()); + } else { + //Load AVURLAsset + //initialize asset using content's URL + setURL(m_observer, m_resources.toEncoded()); + } + + m_state = QMediaPlayer::StoppedState; + if (m_state != oldState) + Q_EMIT stateChanged(m_state); +} + +qint64 AVFMediaPlayerSession::position() const +{ + AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem]; + + if (!playerItem) + return m_requestedPosition != -1 ? m_requestedPosition : 0; + + CMTime time = [playerItem currentTime]; + return static_cast<quint64>(float(time.value) / float(time.timescale) * 1000.0f); +} + +qint64 AVFMediaPlayerSession::duration() const +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + return m_duration; +} + +int AVFMediaPlayerSession::bufferStatus() const +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + return m_bufferStatus; +} + +int AVFMediaPlayerSession::volume() const +{ + return m_volume; +} + +bool AVFMediaPlayerSession::isMuted() const +{ + return m_muted; +} + +void AVFMediaPlayerSession::setAudioAvailable(bool available) +{ + if (m_audioAvailable == available) + return; + + m_audioAvailable = available; + Q_EMIT audioAvailableChanged(available); +} + +bool AVFMediaPlayerSession::isAudioAvailable() const +{ + return m_audioAvailable; +} + +void AVFMediaPlayerSession::setVideoAvailable(bool available) +{ + if (m_videoAvailable == available) + return; + + m_videoAvailable = available; + Q_EMIT videoAvailableChanged(available); +} + +bool AVFMediaPlayerSession::isVideoAvailable() const +{ + return m_videoAvailable; +} + +bool AVFMediaPlayerSession::isSeekable() const +{ + return m_seekable; +} + +void AVFMediaPlayerSession::setSeekable(bool seekable) +{ + if (m_seekable == seekable) + return; + + m_seekable = seekable; + Q_EMIT seekableChanged(seekable); +} + +QMediaTimeRange AVFMediaPlayerSession::availablePlaybackRanges() const +{ + AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem]; + + if (playerItem) { + QMediaTimeRange timeRanges; + + NSArray *ranges = [playerItem loadedTimeRanges]; + for (NSValue *timeRange in ranges) { + CMTimeRange currentTimeRange = [timeRange CMTimeRangeValue]; + qint64 startTime = qint64(float(currentTimeRange.start.value) / currentTimeRange.start.timescale * 1000.0); + timeRanges.addInterval(startTime, startTime + qint64(float(currentTimeRange.duration.value) / currentTimeRange.duration.timescale * 1000.0)); + } + if (!timeRanges.isEmpty()) + return timeRanges; + } + return QMediaTimeRange(0, duration()); +} + +qreal AVFMediaPlayerSession::playbackRate() const +{ + return m_rate; +} + +void AVFMediaPlayerSession::setPlaybackRate(qreal rate) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << rate; +#endif + + if (qFuzzyCompare(m_rate, rate)) + return; + + m_rate = rate; + + AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player]; + if (player && m_state == QMediaPlayer::PlayingState) + [player setRate:m_rate]; + + Q_EMIT playbackRateChanged(m_rate); +} + +void AVFMediaPlayerSession::setPosition(qint64 pos) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << pos; +#endif + + if (pos == position()) + return; + + AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem]; + if (!playerItem) { + m_requestedPosition = pos; + Q_EMIT positionChanged(m_requestedPosition); + return; + } + + if (!isSeekable()) { + if (m_requestedPosition != -1) { + m_requestedPosition = -1; + Q_EMIT positionChanged(position()); + } + return; + } + + pos = qMax(qint64(0), pos); + if (duration() > 0) + pos = qMin(pos, duration()); + + CMTime newTime = [playerItem currentTime]; + newTime.value = (pos / 1000.0f) * newTime.timescale; + [playerItem seekToTime:newTime toleranceBefore:kCMTimeZero toleranceAfter:kCMTimeZero completionHandler:nil]; + + Q_EMIT positionChanged(pos); + + // Reset media status if the current status is EndOfMedia + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + QMediaPlayer::MediaStatus newMediaStatus = (m_state == QMediaPlayer::PausedState) ? QMediaPlayer::BufferedMedia + : QMediaPlayer::LoadedMedia; + Q_EMIT mediaStatusChanged((m_mediaStatus = newMediaStatus)); + } +} + +void AVFMediaPlayerSession::play() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_mediaStatus == QMediaPlayer::NoMedia || m_mediaStatus == QMediaPlayer::InvalidMedia) + return; + + if (m_state == QMediaPlayer::PlayingState) + return; + + if (m_videoOutput) { + m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]); + } + + // Reset media status if the current status is EndOfMedia + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + setPosition(0); + + if (m_mediaStatus == QMediaPlayer::LoadedMedia || m_mediaStatus == QMediaPlayer::BufferedMedia) { + // Setting the rate starts playback + [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate]; + } + + m_state = QMediaPlayer::PlayingState; + processLoadStateChange(); + + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::pause() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_mediaStatus == QMediaPlayer::NoMedia) + return; + + if (m_state == QMediaPlayer::PausedState) + return; + + m_state = QMediaPlayer::PausedState; + + if (m_videoOutput) { + m_videoOutput->setLayer([static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]); + } + + [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] pause]; + + // Reset media status if the current status is EndOfMedia + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + setPosition(0); + + + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::stop() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << "currently: " << m_state; +#endif + + if (m_state == QMediaPlayer::StoppedState) + return; + + // AVPlayer doesn't have stop(), only pause() and play(). + [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] pause]; + setPosition(0); + + if (m_videoOutput) { + m_videoOutput->setLayer(nullptr); + } + + if (m_mediaStatus == QMediaPlayer::BufferedMedia) + Q_EMIT mediaStatusChanged((m_mediaStatus = QMediaPlayer::LoadedMedia)); + + Q_EMIT positionChanged(position()); + Q_EMIT stateChanged((m_state = QMediaPlayer::StoppedState)); +} + +void AVFMediaPlayerSession::setVolume(int volume) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << volume; +#endif + + if (m_volume == volume) + return; + + m_volume = volume; + + AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player]; + if (player) + [player setVolume:volume / 100.0f]; + + Q_EMIT volumeChanged(m_volume); +} + +void AVFMediaPlayerSession::setMuted(bool muted) +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << muted; +#endif + + if (m_muted == muted) + return; + + m_muted = muted; + + AVPlayer *player = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player]; + if (player) + [player setMuted:muted]; + + Q_EMIT mutedChanged(muted); +} + +void AVFMediaPlayerSession::processEOS() +{ + //AVPlayerItem has reached end of track/stream +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + Q_EMIT positionChanged(position()); + m_mediaStatus = QMediaPlayer::EndOfMedia; + m_state = QMediaPlayer::StoppedState; + + // At this point, frames should not be rendered anymore. + // Clear the output layer to make sure of that. + if (m_videoOutput) + m_videoOutput->setLayer(nullptr); + + Q_EMIT mediaStatusChanged(m_mediaStatus); + Q_EMIT stateChanged(m_state); +} + +void AVFMediaPlayerSession::processLoadStateChange(QMediaPlayer::State newState) +{ + AVPlayerStatus currentStatus = [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] status]; + +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO << currentStatus << ", " << m_mediaStatus << ", " << newState; +#endif + + if (m_mediaStatus == QMediaPlayer::NoMedia) + return; + + if (currentStatus == AVPlayerStatusReadyToPlay) { + + QMediaPlayer::MediaStatus newStatus = m_mediaStatus; + + AVPlayerItem *playerItem = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerItem]; + + if (playerItem) { + // Check each track for audio and video content + AVAssetTrack *videoTrack = nil; + NSArray *tracks = playerItem.tracks; + for (AVPlayerItemTrack *track in tracks) { + AVAssetTrack *assetTrack = track.assetTrack; + if (assetTrack) { + if ([assetTrack.mediaType isEqualToString:AVMediaTypeAudio]) + setAudioAvailable(true); + if ([assetTrack.mediaType isEqualToString:AVMediaTypeVideo]) { + setVideoAvailable(true); + if (!videoTrack) + videoTrack = assetTrack; + } + } + } + + setSeekable([[playerItem seekableTimeRanges] count] > 0); + + // Get the native size of the video, and reset the bounds of the player layer + AVPlayerLayer *playerLayer = [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) playerLayer]; + if (videoTrack && playerLayer) { + if (!playerLayer.bounds.size.width || !playerLayer.bounds.size.height) { + playerLayer.bounds = CGRectMake(0.0f, 0.0f, + videoTrack.naturalSize.width, + videoTrack.naturalSize.height); + } + + if (m_videoOutput && newState != QMediaPlayer::StoppedState) { + m_videoOutput->setLayer(playerLayer); + } + } + + if (m_requestedPosition != -1) { + setPosition(m_requestedPosition); + m_requestedPosition = -1; + } + } + + newStatus = (newState != QMediaPlayer::StoppedState) ? QMediaPlayer::BufferedMedia + : QMediaPlayer::LoadedMedia; + + if (newStatus != m_mediaStatus) + Q_EMIT mediaStatusChanged((m_mediaStatus = newStatus)); + + } + + if (newState == QMediaPlayer::PlayingState && [static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player]) { + // Setting the rate is enough to start playback, no need to call play() + [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate]; + } +} + + +void AVFMediaPlayerSession::processLoadStateChange() +{ + processLoadStateChange(m_state); +} + + +void AVFMediaPlayerSession::processLoadStateFailure() +{ + Q_EMIT stateChanged((m_state = QMediaPlayer::StoppedState)); +} + +void AVFMediaPlayerSession::processBufferStateChange(int bufferStatus) +{ + if (bufferStatus == m_bufferStatus) + return; + + auto status = m_mediaStatus; + // Buffered -> unbuffered. + if (!bufferStatus) { + status = QMediaPlayer::StalledMedia; + } else if (status == QMediaPlayer::StalledMedia) { + status = QMediaPlayer::BufferedMedia; + // Resume playback. + if (m_state == QMediaPlayer::PlayingState) + [[static_cast<AVFMediaPlayerSessionObserver*>(m_observer) player] setRate:m_rate]; + } + + if (m_mediaStatus != status) + Q_EMIT mediaStatusChanged(m_mediaStatus = status); + + m_bufferStatus = bufferStatus; + Q_EMIT bufferStatusChanged(bufferStatus); +} + +void AVFMediaPlayerSession::processDurationChange(qint64 duration) +{ + if (duration == m_duration) + return; + + m_duration = duration; + Q_EMIT durationChanged(duration); +} + +void AVFMediaPlayerSession::processPositionChange() +{ + if (m_state == QMediaPlayer::StoppedState) + return; + + Q_EMIT positionChanged(position()); +} + +void AVFMediaPlayerSession::processMediaLoadError() +{ + if (m_requestedPosition != -1) { + m_requestedPosition = -1; + Q_EMIT positionChanged(position()); + } + + Q_EMIT mediaStatusChanged((m_mediaStatus = QMediaPlayer::InvalidMedia)); + + Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media")); +} + +void AVFMediaPlayerSession::streamReady() +{ + setStreamURL(m_observer, m_resources.toEncoded()); +} + +void AVFMediaPlayerSession::streamDestroyed() +{ + resetStream(nullptr); +} + +void AVFMediaPlayerSession::resetStream(QIODevice *stream) +{ + if (m_mediaStream) { + disconnect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayerSession::streamReady); + disconnect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayerSession::streamDestroyed); + } + + m_mediaStream = stream; + + if (m_mediaStream) { + connect(m_mediaStream, &QIODevice::readyRead, this, &AVFMediaPlayerSession::streamReady); + connect(m_mediaStream, &QIODevice::destroyed, this, &AVFMediaPlayerSession::streamDestroyed); + } +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h new file mode 100644 index 000000000..c3728c524 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfmediaplayersession_p.h @@ -0,0 +1,172 @@ +/**************************************************************************** +** +** 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 AVFMEDIAPLAYERSESSION_H +#define AVFMEDIAPLAYERSESSION_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/QObject> +#include <QtCore/QByteArray> +#include <QtCore/QSet> +#include <QtCore/QResource> + +#include <QtMultimedia/QMediaPlayerControl> +#include <QtMultimedia/QMediaPlayer> + +QT_BEGIN_NAMESPACE + +class AVFMediaPlayerService; +class AVFVideoOutput; + +class AVFMediaPlayerSession : public QObject +{ + Q_OBJECT +public: + AVFMediaPlayerSession(AVFMediaPlayerService *service, QObject *parent = nullptr); + virtual ~AVFMediaPlayerSession(); + + void setVideoOutput(AVFVideoOutput *output); + void *currentAssetHandle(); + + QMediaPlayer::State state() const; + QMediaPlayer::MediaStatus mediaStatus() const; + + QUrl media() const; + QIODevice *mediaStream() const; + void setMedia(const QUrl &content, QIODevice *stream); + + qint64 position() const; + qint64 duration() const; + + int bufferStatus() const; + + int volume() const; + bool isMuted() const; + + bool isAudioAvailable() const; + bool isVideoAvailable() const; + + bool isSeekable() const; + QMediaTimeRange availablePlaybackRanges() const; + + qreal playbackRate() const; + +public Q_SLOTS: + void setPlaybackRate(qreal rate); + + void setPosition(qint64 pos); + + void play(); + void pause(); + void stop(); + + void setVolume(int volume); + void setMuted(bool muted); + + void processEOS(); + void processLoadStateChange(QMediaPlayer::State newState); + void processPositionChange(); + void processMediaLoadError(); + + void processLoadStateChange(); + void processLoadStateFailure(); + + void processBufferStateChange(int bufferStatus); + + void processDurationChange(qint64 duration); + + void streamReady(); + void streamDestroyed(); + +Q_SIGNALS: + void positionChanged(qint64 position); + void durationChanged(qint64 duration); + void stateChanged(QMediaPlayer::State newState); + void bufferStatusChanged(int bufferStatus); + void mediaStatusChanged(QMediaPlayer::MediaStatus status); + void volumeChanged(int volume); + void mutedChanged(bool muted); + void audioAvailableChanged(bool audioAvailable); + void videoAvailableChanged(bool videoAvailable); + void playbackRateChanged(qreal rate); + void seekableChanged(bool seekable); + void error(int error, const QString &errorString); + +private: + void setAudioAvailable(bool available); + void setVideoAvailable(bool available); + void setSeekable(bool seekable); + void resetStream(QIODevice *stream = nullptr); + + AVFMediaPlayerService *m_service; + AVFVideoOutput *m_videoOutput; + + QMediaPlayer::State m_state; + QMediaPlayer::MediaStatus m_mediaStatus; + QIODevice *m_mediaStream; + QUrl m_resources; + + bool m_muted; + bool m_tryingAsync; + int m_volume; + qreal m_rate; + qint64 m_requestedPosition; + + qint64 m_duration; + int m_bufferStatus; + bool m_videoAvailable; + bool m_audioAvailable; + bool m_seekable; + + void *m_observer; +}; + +QT_END_NAMESPACE + +#endif // AVFMEDIAPLAYERSESSION_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm new file mode 100644 index 000000000..88c83f1a8 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer.mm @@ -0,0 +1,464 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfvideoframerenderer_p.h" + +#include <QtMultimedia/qabstractvideosurface.h> +#include <QtOpenGL/QOpenGLFramebufferObject> +#include <QtGui/QWindow> +#include <QOpenGLShaderProgram> + +#ifdef QT_DEBUG_AVF +#include <QtCore/qdebug.h> +#endif + +#import <CoreVideo/CVBase.h> +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +AVFVideoFrameRenderer::AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent) + : QObject(parent) + , m_videoLayerRenderer(nullptr) + , m_surface(surface) + , m_offscreenSurface(nullptr) + , m_glContext(nullptr) + , m_currentBuffer(1) + , m_isContextShared(true) +{ + m_fbo[0] = nullptr; + m_fbo[1] = nullptr; +} + +AVFVideoFrameRenderer::~AVFVideoFrameRenderer() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + + [m_videoLayerRenderer release]; + [m_metalDevice release]; + delete m_fbo[0]; + delete m_fbo[1]; + delete m_offscreenSurface; + delete m_glContext; + + if (m_useCoreProfile) { + glDeleteVertexArrays(1, &m_quadVao); + glDeleteBuffers(2, m_quadVbos); + delete m_shader; + } +} + +quint64 AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer) +{ + //Is layer valid + if (!layer) + return 0; + + //If the glContext isn't shared, it doesn't make sense to return a texture for us + if (m_offscreenSurface && !m_isContextShared) + return 0; + + QOpenGLFramebufferObject *fbo = initRenderer(layer); + + if (!fbo) + return 0; + + renderLayerToFBO(layer, fbo); + if (m_glContext) + m_glContext->doneCurrent(); + + return fbo->texture(); +} + +quint64 AVFVideoFrameRenderer::renderLayerToMTLTexture(AVPlayerLayer *layer) +{ + m_targetSize = QSize(layer.bounds.size.width, layer.bounds.size.height); + + if (!m_metalDevice) + m_metalDevice = MTLCreateSystemDefaultDevice(); + + if (!m_metalTexture) { + auto desc = [[MTLTextureDescriptor alloc] init]; + desc.textureType = MTLTextureType2D; + desc.width = NSUInteger(m_targetSize.width()); + desc.height = NSUInteger(m_targetSize.height()); + desc.resourceOptions = MTLResourceStorageModePrivate; + desc.usage = MTLTextureUsageRenderTarget; + desc.pixelFormat = MTLPixelFormatRGBA8Unorm; + + m_metalTexture = [m_metalDevice newTextureWithDescriptor: desc]; + [desc release]; + } + + if (!m_videoLayerRenderer) { + m_videoLayerRenderer = [CARenderer rendererWithMTLTexture:m_metalTexture options:nil]; + [m_videoLayerRenderer retain]; + } + + if (m_videoLayerRenderer.layer != layer) { + m_videoLayerRenderer.layer = layer; + m_videoLayerRenderer.bounds = layer.bounds; + } + + [m_videoLayerRenderer beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL]; + [m_videoLayerRenderer addUpdateRect:layer.bounds]; + [m_videoLayerRenderer render]; + [m_videoLayerRenderer endFrame]; + + return quint64(m_metalTexture); +} + +QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer) +{ + //Is layer valid + if (!layer) { + return QImage(); + } + + QOpenGLFramebufferObject *fbo = initRenderer(layer); + + if (!fbo) + return QImage(); + + renderLayerToFBO(layer, fbo); + QImage fboImage = fbo->toImage(); + if (m_glContext) + m_glContext->doneCurrent(); + + return fboImage; +} + +QOpenGLFramebufferObject *AVFVideoFrameRenderer::initRenderer(AVPlayerLayer *layer) +{ + + //Get size from AVPlayerLayer + m_targetSize = QSize(layer.bounds.size.width, layer.bounds.size.height); + + 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 + delete m_offscreenSurface; + 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(); + + delete m_glContext; + m_glContext = new QOpenGLContext(); + m_glContext->setFormat(m_offscreenSurface->requestedFormat()); + + if (shareContext) { + m_glContext->setShareContext(shareContext); + m_isContextShared = true; + } else { +#ifdef QT_DEBUG_AVF + qWarning("failed to get Render Thread context"); +#endif + m_isContextShared = false; + } + if (!m_glContext->create()) { + qWarning("failed to create QOpenGLContext"); + return nullptr; + } + + // CARenderer must be re-created with different current context, so release it now. + // See lines below where m_videoLayerRenderer is constructed. + if (m_videoLayerRenderer) { + [m_videoLayerRenderer release]; + m_videoLayerRenderer = nullptr; + } + + if (m_useCoreProfile) { + glDeleteVertexArrays(1, &m_quadVao); + glDeleteBuffers(2, m_quadVbos); + delete m_shader; + m_shader = nullptr; + } + } + + //Need current context + if (m_glContext) + m_glContext->makeCurrent(m_offscreenSurface); + + if (!m_metalDevice) + m_metalDevice = MTLCreateSystemDefaultDevice(); + + if (@available(macOS 10.13, *)) { + m_useCoreProfile = m_metalDevice && (QOpenGLContext::currentContext()->format().profile() == + QSurfaceFormat::CoreProfile); + } else { + m_useCoreProfile = false; + } + + // Create the CARenderer if needed for no Core OpenGL + if (!m_videoLayerRenderer) { + if (!m_useCoreProfile) { + m_videoLayerRenderer = [CARenderer rendererWithCGLContext: CGLGetCurrentContext() + options: nil]; + [m_videoLayerRenderer retain]; + } else if (@available(macOS 10.13, *)) { + // This is always true when m_useCoreProfile is true, but the compiler wants the check + // anyway + // Setup Core OpenGL shader, VAO, VBOs and metal renderer + m_shader = new QOpenGLShaderProgram(); + m_shader->create(); + if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, R"(#version 150 core + in vec2 qt_VertexPosition; + in vec2 qt_VertexTexCoord; + out vec2 qt_TexCoord; + void main() + { + qt_TexCoord = qt_VertexTexCoord; + gl_Position = vec4(qt_VertexPosition, 0.0f, 1.0f); + })")) { + qCritical() << "Vertex shader compilation failed" << m_shader->log(); + } + if (!m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, R"(#version 150 core + in vec2 qt_TexCoord; + out vec4 fragColor; + uniform sampler2DRect videoFrame; + void main(void) + { + ivec2 textureDim = textureSize(videoFrame); + fragColor = texture(videoFrame, qt_TexCoord * textureDim); + })")) { + qCritical() << "Fragment shader compilation failed" << m_shader->log(); + } + + // Setup quad where the video frame will be attached + GLfloat vertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + }; + + GLfloat uvs[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + }; + + glGenVertexArrays(1, &m_quadVao); + glBindVertexArray(m_quadVao); + + // Create vertex buffer objects for vertices + glGenBuffers(2, m_quadVbos); + + // Setup vertices + glBindBuffer(GL_ARRAY_BUFFER, m_quadVbos[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); + glEnableVertexAttribArray(0); + + // Setup uvs + glBindBuffer(GL_ARRAY_BUFFER, m_quadVbos[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); + glEnableVertexAttribArray(1); + + glBindVertexArray(0); + + // Setup shared Metal/OpenGL pixel buffer and textures + m_NSGLContext = QOpenGLContext::currentContext()->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext(); + m_CGLPixelFormat = m_NSGLContext.pixelFormat.CGLPixelFormatObj; + + NSDictionary* cvBufferProperties = @{ + static_cast<NSString*>(kCVPixelBufferOpenGLCompatibilityKey) : @YES, + static_cast<NSString*>(kCVPixelBufferMetalCompatibilityKey): @YES, + }; + + CVPixelBufferCreate(kCFAllocatorDefault, static_cast<size_t>(m_targetSize.width()), + static_cast<size_t>(m_targetSize.height()), kCVPixelFormatType_32BGRA, + static_cast<CFDictionaryRef>(cvBufferProperties), &m_CVPixelBuffer); + + m_textureName = createGLTexture(reinterpret_cast<CGLContextObj>(m_NSGLContext.CGLContextObj), + m_CGLPixelFormat, m_CVGLTextureCache, m_CVPixelBuffer, + m_CVGLTexture); + m_metalTexture = createMetalTexture(m_metalDevice, m_CVMTLTextureCache, m_CVPixelBuffer, + MTLPixelFormatBGRA8Unorm, + static_cast<size_t>(m_targetSize.width()), + static_cast<size_t>(m_targetSize.height()), + m_CVMTLTexture); + + m_videoLayerRenderer = [CARenderer rendererWithMTLTexture:m_metalTexture options:nil]; + [m_videoLayerRenderer retain]; + } + } + + //Set/Change render source if needed + if (m_videoLayerRenderer.layer != layer) { + m_videoLayerRenderer.layer = layer; + m_videoLayerRenderer.bounds = layer.bounds; + } + + //Do we have FBO's already? + if ((!m_fbo[0] && !m_fbo[0]) || (m_fbo[0]->size() != m_targetSize)) { + delete m_fbo[0]; + delete m_fbo[1]; + m_fbo[0] = new QOpenGLFramebufferObject(m_targetSize); + m_fbo[1] = new QOpenGLFramebufferObject(m_targetSize); + } + + //Switch buffer target + m_currentBuffer = !m_currentBuffer; + return m_fbo[m_currentBuffer]; +} + +void AVFVideoFrameRenderer::renderLayerToFBO(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo) +{ + //Start Rendering + //NOTE: This rendering method will NOT work on iOS as there is no CARenderer in iOS + if (!fbo->bind()) { + qWarning("AVFVideoRender FBO failed to bind"); + return; + } + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, m_targetSize.width(), m_targetSize.height()); + + if (m_useCoreProfile) { + CGLLockContext(m_NSGLContext.CGLContextObj); + m_shader->bind(); + glBindVertexArray(m_quadVao); + } else { + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + // Render to FBO with inverted Y + glOrtho(0.0, m_targetSize.width(), 0.0, m_targetSize.height(), 0.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + } + + [m_videoLayerRenderer beginFrameAtTime:CACurrentMediaTime() timeStamp:nullptr]; + [m_videoLayerRenderer addUpdateRect:layer.bounds]; + [m_videoLayerRenderer render]; + [m_videoLayerRenderer endFrame]; + + if (m_useCoreProfile) { + glActiveTexture(0); + glBindTexture(GL_TEXTURE_RECTANGLE, m_textureName); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glBindTexture(GL_TEXTURE_RECTANGLE, 0); + + glBindVertexArray(0); + + m_shader->release(); + + CGLFlushDrawable(m_NSGLContext.CGLContextObj); + CGLUnlockContext(m_NSGLContext.CGLContextObj); + } else { + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + } + + glFinish(); //Rendering needs to be done before passing texture to video frame + + fbo->release(); +} + +GLuint AVFVideoFrameRenderer::createGLTexture(CGLContextObj cglContextObj, CGLPixelFormatObj cglPixelFormtObj, CVOpenGLTextureCacheRef cvglTextureCache, + CVPixelBufferRef cvPixelBufferRef, CVOpenGLTextureRef cvOpenGLTextureRef) +{ + CVReturn cvret; + // Create an OpenGL CoreVideo texture cache from the pixel buffer. + cvret = CVOpenGLTextureCacheCreate( + kCFAllocatorDefault, + nil, + cglContextObj, + cglPixelFormtObj, + nil, + &cvglTextureCache); + + // Create a CVPixelBuffer-backed OpenGL texture image from the texture cache. + cvret = CVOpenGLTextureCacheCreateTextureFromImage( + kCFAllocatorDefault, + cvglTextureCache, + cvPixelBufferRef, + nil, + &cvOpenGLTextureRef); + + // Get an OpenGL texture name from the CVPixelBuffer-backed OpenGL texture image. + return CVOpenGLTextureGetName(cvOpenGLTextureRef); +} + +id<MTLTexture> AVFVideoFrameRenderer::createMetalTexture(id<MTLDevice> mtlDevice, CVMetalTextureCacheRef cvMetalTextureCacheRef, CVPixelBufferRef cvPixelBufferRef, + MTLPixelFormat pixelFormat, size_t width, size_t height, CVMetalTextureRef cvMetalTextureRef) +{ + CVReturn cvret; + // Create a Metal Core Video texture cache from the pixel buffer. + cvret = CVMetalTextureCacheCreate( + kCFAllocatorDefault, + nil, + mtlDevice, + nil, + &cvMetalTextureCacheRef); + + // Create a CoreVideo pixel buffer backed Metal texture image from the texture cache. + cvret = CVMetalTextureCacheCreateTextureFromImage( + kCFAllocatorDefault, + cvMetalTextureCacheRef, + cvPixelBufferRef, nil, + pixelFormat, + width, height, + 0, + &cvMetalTextureRef); + + // Get a Metal texture using the CoreVideo Metal texture reference. + return CVMetalTextureGetTexture(cvMetalTextureRef); +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm new file mode 100644 index 000000000..cbaa1aa11 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios.mm @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfvideoframerenderer_ios.h" + +#include <QtMultimedia/qabstractvideosurface.h> +#include <QtOpenGL/QOpenGLFramebufferObject> +#include <QtOpenGL/QOpenGLShaderProgram> +#include <QtGui/QOffscreenSurface> + +#ifdef QT_DEBUG_AVF +#include <QtCore/qdebug.h> +#endif + +#import <CoreVideo/CVBase.h> +#import <AVFoundation/AVFoundation.h> +QT_USE_NAMESPACE + +AVFVideoFrameRenderer::AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent) + : QObject(parent) + , m_glContext(nullptr) + , m_offscreenSurface(nullptr) + , m_surface(surface) + , m_textureCache(nullptr) + , m_videoOutput(nullptr) + , m_isContextShared(true) +{ +} + +AVFVideoFrameRenderer::~AVFVideoFrameRenderer() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + + [m_videoOutput release]; // sending to nil is fine + if (m_textureCache) + CFRelease(m_textureCache); + if (m_metalTextureCache) + CFRelease(m_metalTextureCache); + [m_metalDevice release]; + delete m_offscreenSurface; + delete m_glContext; +} + +void AVFVideoFrameRenderer::setPlayerLayer(AVPlayerLayer *layer) +{ + Q_UNUSED(layer); + if (m_videoOutput) { + [m_videoOutput release]; + m_videoOutput = nullptr; + // will be re-created in first call to copyPixelBufferFromLayer + } +} + +quint64 AVFVideoFrameRenderer::renderLayerToMTLTexture(AVPlayerLayer *layer) +{ + if (!m_metalDevice) + m_metalDevice = MTLCreateSystemDefaultDevice(); + + if (!m_metalTextureCache) { + CVReturn err = CVMetalTextureCacheCreate(kCFAllocatorDefault, nullptr, + m_metalDevice, nullptr, &m_metalTextureCache); + if (err) { + qWarning() << "Error at CVMetalTextureCacheCreate" << err; + return 0; + } + } + + size_t width = 0, height = 0; + CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height); + + if (!pixelBuffer) + return 0; + + CVMetalTextureCacheFlush(m_metalTextureCache, 0); + + CVMetalTextureRef texture = nil; + CVReturn err = CVMetalTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_metalTextureCache, pixelBuffer, NULL, + MTLPixelFormatBGRA8Unorm_sRGB, width, height, 0, &texture); + + if (!texture || err) + qWarning("CVMetalTextureCacheCreateTextureFromImage failed (error: %d)", err); + + CVPixelBufferRelease(pixelBuffer); + quint64 tex = 0; + if (texture) { + tex = quint64(CVMetalTextureGetTexture(texture)); + CFRelease(texture); + } + + return tex; +} + +quint64 AVFVideoFrameRenderer::renderLayerToTexture(AVPlayerLayer *layer) +{ + initRenderer(); + + // If the glContext isn't shared, it doesn't make sense to return a texture for us + if (!m_isContextShared) + return 0; + + size_t dummyWidth = 0, dummyHeight = 0; + auto texture = createCacheTextureFromLayer(layer, dummyWidth, dummyHeight); + auto tex = quint64(CVOGLTextureGetName(texture)); + CFRelease(texture); + + return tex; +} + +static NSString* const AVF_PIXEL_FORMAT_KEY = (NSString*)kCVPixelBufferPixelFormatTypeKey; +static NSNumber* const AVF_PIXEL_FORMAT_VALUE = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA]; +static NSDictionary* const AVF_OUTPUT_SETTINGS = [NSDictionary dictionaryWithObject:AVF_PIXEL_FORMAT_VALUE forKey:AVF_PIXEL_FORMAT_KEY]; + + +CVPixelBufferRef AVFVideoFrameRenderer::copyPixelBufferFromLayer(AVPlayerLayer *layer, + size_t& width, size_t& height) +{ + //Is layer valid + if (!layer) { +#ifdef QT_DEBUG_AVF + qWarning("copyPixelBufferFromLayer: invalid layer"); +#endif + return nullptr; + } + + if (!m_videoOutput) { + m_videoOutput = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:AVF_OUTPUT_SETTINGS]; + [m_videoOutput setDelegate:nil queue:nil]; + AVPlayerItem * item = [[layer player] currentItem]; + [item addOutput:m_videoOutput]; + } + + CFTimeInterval currentCAFrameTime = CACurrentMediaTime(); + CMTime currentCMFrameTime = [m_videoOutput itemTimeForHostTime:currentCAFrameTime]; + // happens when buffering / loading + if (CMTimeCompare(currentCMFrameTime, kCMTimeZero) < 0) { + return nullptr; + } + + CVPixelBufferRef pixelBuffer = [m_videoOutput copyPixelBufferForItemTime:currentCMFrameTime + itemTimeForDisplay:nil]; + if (!pixelBuffer) { +#ifdef QT_DEBUG_AVF + qWarning("copyPixelBufferForItemTime returned nil"); + CMTimeShow(currentCMFrameTime); +#endif + return nullptr; + } + + width = CVPixelBufferGetWidth(pixelBuffer); + height = CVPixelBufferGetHeight(pixelBuffer); + return pixelBuffer; +} + +CVOGLTextureRef AVFVideoFrameRenderer::createCacheTextureFromLayer(AVPlayerLayer *layer, + size_t& width, size_t& height) +{ + CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height); + + if (!pixelBuffer) + return nullptr; + + CVOGLTextureCacheFlush(m_textureCache, 0); + + CVOGLTextureRef texture = nullptr; + CVReturn err = CVOGLTextureCacheCreateTextureFromImage(kCFAllocatorDefault, m_textureCache, pixelBuffer, nullptr, + GL_TEXTURE_2D, GL_RGBA, + (GLsizei) width, (GLsizei) height, + GL_BGRA, GL_UNSIGNED_BYTE, 0, + &texture); + + if (!texture || err) { +#ifdef QT_DEBUG_AVF + qWarning("CVOGLTextureCacheCreateTextureFromImage failed (error: %d)", err); +#endif + } + + CVPixelBufferRelease(pixelBuffer); + + return texture; +} + +QImage AVFVideoFrameRenderer::renderLayerToImage(AVPlayerLayer *layer) +{ + size_t width = 0; + size_t height = 0; + CVPixelBufferRef pixelBuffer = copyPixelBufferFromLayer(layer, width, height); + + if (!pixelBuffer) + return QImage(); + + OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); + if (pixelFormat != kCVPixelFormatType_32BGRA) { +#ifdef QT_DEBUG_AVF + qWarning("CVPixelBuffer format is not BGRA32 (got: %d)", static_cast<quint32>(pixelFormat)); +#endif + return QImage(); + } + + CVPixelBufferLockBaseAddress(pixelBuffer, 0); + char *data = (char *)CVPixelBufferGetBaseAddress(pixelBuffer); + size_t stride = CVPixelBufferGetBytesPerRow(pixelBuffer); + + // format here is not relevant, only using for storage + QImage img = QImage(width, height, QImage::Format_ARGB32); + for (size_t j = 0; j < height; j++) { + memcpy(img.scanLine(j), data, width * 4); + data += stride; + } + + CVPixelBufferUnlockBaseAddress(pixelBuffer, 0); + CVPixelBufferRelease(pixelBuffer); + return img; +} + +void AVFVideoFrameRenderer::initRenderer() +{ + // even for using a texture directly, we need to be able to make a context current, + // so we need an offscreen, and we shouldn't assume we can make the surface context + // current on that offscreen, so use our own (sharing with it). Slightly + // excessive but no performance penalty and makes the QImage path easier to maintain + + //Make sure we have an OpenGL context to make current + if (!m_glContext) { + //Create OpenGL context and set share context from surface + QOpenGLContext *shareContext = nullptr; + if (m_surface) { + shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>()); + } + + m_glContext = new QOpenGLContext(); + if (shareContext) { + m_glContext->setShareContext(shareContext); + m_isContextShared = true; + } else { +#ifdef QT_DEBUG_AVF + qWarning("failed to get Render Thread context"); +#endif + m_isContextShared = false; + } + if (!m_glContext->create()) { +#ifdef QT_DEBUG_AVF + qWarning("failed to create QOpenGLContext"); +#endif + return; + } + } + + if (!m_offscreenSurface) { + m_offscreenSurface = new QOffscreenSurface(); + m_offscreenSurface->setFormat(m_glContext->format()); + m_offscreenSurface->create(); + } + + //Need current context + m_glContext->makeCurrent(m_offscreenSurface); + + if (!m_textureCache) { + // Create a new open gl texture cache + CVReturn err = CVOGLTextureCacheCreate(kCFAllocatorDefault, nullptr, + [EAGLContext currentContext], + nullptr, &m_textureCache); + if (err) { + #ifdef QT_DEBUG_AVF + qWarning("Error at CVOGLTextureCacheCreate %d", err); + #endif + } + } + +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h new file mode 100644 index 000000000..b69e33ceb --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_ios_p.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** 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 AVFVIDEOFRAMERENDERER_H +#define AVFVIDEOFRAMERENDERER_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/QObject> +#include <QtGui/QImage> +#include <QtGui/QOpenGLContext> +#include <QtCore/QSize> + +#import "Metal/Metal.h" +#import "MetalKit/MetalKit.h" + +@class AVPlayerLayer; +@class AVPlayerItemVideoOutput; + +QT_BEGIN_NAMESPACE + +class QOpenGLContext; +class QOpenGLFramebufferObject; +class QOpenGLShaderProgram; +class QOffscreenSurface; +class QAbstractVideoSurface; + +typedef struct __CVBuffer *CVBufferRef; +typedef CVBufferRef CVImageBufferRef; +typedef CVImageBufferRef CVPixelBufferRef; +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +typedef struct __CVOpenGLESTextureCache *CVOpenGLESTextureCacheRef; +typedef CVImageBufferRef CVOpenGLESTextureRef; +// helpers to avoid boring if def +typedef CVOpenGLESTextureCacheRef CVOGLTextureCacheRef; +typedef CVOpenGLESTextureRef CVOGLTextureRef; +#define CVOGLTextureGetTarget CVOpenGLESTextureGetTarget +#define CVOGLTextureGetName CVOpenGLESTextureGetName +#define CVOGLTextureCacheCreate CVOpenGLESTextureCacheCreate +#define CVOGLTextureCacheCreateTextureFromImage CVOpenGLESTextureCacheCreateTextureFromImage +#define CVOGLTextureCacheFlush CVOpenGLESTextureCacheFlush +#else +typedef struct __CVOpenGLTextureCache *CVOpenGLTextureCacheRef; +typedef CVImageBufferRef CVOpenGLTextureRef; +// helpers to avoid boring if def +typedef CVOpenGLTextureCacheRef CVOGLTextureCacheRef; +typedef CVOpenGLTextureRef CVOGLTextureRef; +#define CVOGLTextureGetTarget CVOpenGLTextureGetTarget +#define CVOGLTextureGetName CVOpenGLTextureGetName +#define CVOGLTextureCacheCreate CVOpenGLTextureCacheCreate +#define CVOGLTextureCacheCreateTextureFromImage CVOpenGLTextureCacheCreateTextureFromImage +#define CVOGLTextureCacheFlush CVOpenGLTextureCacheFlush +#endif + +class AVFVideoFrameRenderer : public QObject +{ +public: + AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent = nullptr); + + virtual ~AVFVideoFrameRenderer(); + + void setPlayerLayer(AVPlayerLayer *layer); + + quint64 renderLayerToTexture(AVPlayerLayer *layer); + quint64 renderLayerToMTLTexture(AVPlayerLayer *layer); + QImage renderLayerToImage(AVPlayerLayer *layer); + +private: + void initRenderer(); + CVPixelBufferRef copyPixelBufferFromLayer(AVPlayerLayer *layer, size_t& width, size_t& height); + CVOGLTextureRef createCacheTextureFromLayer(AVPlayerLayer *layer, size_t& width, size_t& height); + + QOpenGLContext *m_glContext; + QOffscreenSurface *m_offscreenSurface; + QAbstractVideoSurface *m_surface; + CVOGLTextureCacheRef m_textureCache; + AVPlayerItemVideoOutput* m_videoOutput; + bool m_isContextShared; + + id<MTLDevice> m_metalDevice = nil; + CVMetalTextureCacheRef m_metalTextureCache = nil; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOFRAMERENDERER_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h new file mode 100644 index 000000000..465c1e563 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideoframerenderer_p.h @@ -0,0 +1,137 @@ +/**************************************************************************** +** +** 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 AVFVIDEOFRAMERENDERER_H +#define AVFVIDEOFRAMERENDERER_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/QObject> +#include <QtGui/QImage> +#include <QtGui/QOpenGLContext> +#include <QtCore/QSize> + +#import "Metal/Metal.h" +#import "MetalKit/MetalKit.h" + +@class CARenderer; +@class AVPlayerLayer; + +QT_BEGIN_NAMESPACE + +class QOpenGLFramebufferObject; +class QOpenGLShaderProgram; +class QWindow; +class QOpenGLContext; +class QAbstractVideoSurface; + +class AVFVideoFrameRenderer : public QObject +{ +public: + AVFVideoFrameRenderer(QAbstractVideoSurface *surface, QObject *parent = nullptr); + + virtual ~AVFVideoFrameRenderer(); + + quint64 renderLayerToTexture(AVPlayerLayer *layer); + quint64 renderLayerToMTLTexture(AVPlayerLayer *layer); + QImage renderLayerToImage(AVPlayerLayer *layer); + + static GLuint createGLTexture(CGLContextObj cglContextObj, CGLPixelFormatObj cglPixelFormtObj, + CVOpenGLTextureCacheRef cvglTextureCache, + CVPixelBufferRef cvPixelBufferRef, + CVOpenGLTextureRef cvOpenGLTextureRef); + + static id<MTLTexture> createMetalTexture(id<MTLDevice> mtlDevice, + CVMetalTextureCacheRef cvMetalTextureCacheRef, + CVPixelBufferRef cvPixelBufferRef, + MTLPixelFormat pixelFormat, size_t width, size_t height, + CVMetalTextureRef cvMetalTextureRef); + +private: + QOpenGLFramebufferObject* initRenderer(AVPlayerLayer *layer); + void renderLayerToFBO(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo); + void renderLayerToFBOCoreOpenGL(AVPlayerLayer *layer, QOpenGLFramebufferObject *fbo); + + CARenderer *m_videoLayerRenderer; + QAbstractVideoSurface *m_surface; + QOpenGLFramebufferObject *m_fbo[2]; + QOpenGLShaderProgram *m_shader = nullptr; + QWindow *m_offscreenSurface; + QOpenGLContext *m_glContext; + QSize m_targetSize; + + bool m_useCoreProfile = false; + + // Shared pixel buffer + CVPixelBufferRef m_CVPixelBuffer; + + // OpenGL Texture + CVOpenGLTextureCacheRef m_CVGLTextureCache; + CVOpenGLTextureRef m_CVGLTexture; + CGLPixelFormatObj m_CGLPixelFormat; + GLuint m_textureName = 0; + + // Metal Texture + CVMetalTextureRef m_CVMTLTexture; + CVMetalTextureCacheRef m_CVMTLTextureCache; + + NSOpenGLContext *m_NSGLContext = nullptr; + + GLuint m_quadVao = 0; + GLuint m_quadVbos[2]; + + uint m_currentBuffer; + bool m_isContextShared; + + id<MTLDevice> m_metalDevice = nil; + id<MTLTexture> m_metalTexture = nil; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOFRAMERENDERER_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm new file mode 100644 index 000000000..ef12f07c1 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput.mm @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfvideooutput_p.h" + +QT_USE_NAMESPACE diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h new file mode 100644 index 000000000..d1dd94460 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideooutput_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** 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 AVFVIDEOOUTPUT_H +#define AVFVIDEOOUTPUT_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/qobject.h> + +QT_BEGIN_NAMESPACE + +class AVFVideoOutput +{ +public: + virtual ~AVFVideoOutput() {} + virtual void setLayer(void *playerLayer) = 0; +}; + +#define AVFVideoOutput_iid \ + "org.qt-project.qt.AVFVideoOutput/5.0" +Q_DECLARE_INTERFACE(AVFVideoOutput, AVFVideoOutput_iid) + +QT_END_NAMESPACE + +#endif // AVFVIDEOOUTPUT_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm new file mode 100644 index 000000000..4cb467296 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol.mm @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfvideorenderercontrol_p.h" +#include "avfdisplaylink_p.h" + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) +#include "avfvideoframerenderer_ios_p.h" +#else +#include "avfvideoframerenderer_p.h" +#endif + +#include <QtMultimedia/qabstractvideobuffer.h> +#include <QtMultimedia/qabstractvideosurface.h> +#include <QtMultimedia/qvideosurfaceformat.h> + +#include <private/qimagevideobuffer_p.h> + +#include <QtCore/qdebug.h> + +#import <AVFoundation/AVFoundation.h> + +QT_USE_NAMESPACE + +class TextureVideoBuffer : public QAbstractVideoBuffer +{ +public: + TextureVideoBuffer(HandleType type, quint64 tex) + : QAbstractVideoBuffer(type) + , m_texture(tex) + {} + + virtual ~TextureVideoBuffer() + { + } + + MapMode mapMode() const { return NotMapped; } + MapData map(MapMode mode) override { return {}; } + void unmap() {} + + QVariant handle() const + { + return QVariant::fromValue<unsigned long long>(m_texture); + } + +private: + quint64 m_texture; +}; + +AVFVideoRendererControl::AVFVideoRendererControl(QObject *parent) + : QVideoRendererControl(parent) + , m_surface(nullptr) + , m_playerLayer(nullptr) + , m_frameRenderer(nullptr) + , m_enableOpenGL(false) + , m_enableMetal(false) + +{ + m_displayLink = new AVFDisplayLink(this); + connect(m_displayLink, SIGNAL(tick(CVTimeStamp)), SLOT(updateVideoFrame(CVTimeStamp))); +} + +AVFVideoRendererControl::~AVFVideoRendererControl() +{ +#ifdef QT_DEBUG_AVF + qDebug() << Q_FUNC_INFO; +#endif + m_displayLink->stop(); + [static_cast<AVPlayerLayer*>(m_playerLayer) release]; +} + +QAbstractVideoSurface *AVFVideoRendererControl::surface() const +{ + return m_surface; +} + +void AVFVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ +#ifdef QT_DEBUG_AVF + qDebug() << "Set video surface" << surface; +#endif + + //When we have a valid surface, we can setup a frame renderer + //and schedule surface updates with the display link. + if (surface == m_surface) + return; + + QMutexLocker locker(&m_mutex); + + if (m_surface && m_surface->isActive()) + m_surface->stop(); + + m_surface = surface; + + //If the surface changed, then the current frame renderer is no longer valid + delete m_frameRenderer; + m_frameRenderer = nullptr; + + //If there is now no surface to render too + if (m_surface == nullptr) { + m_displayLink->stop(); + return; + } + + //Surface changed, so we need a new frame renderer + m_frameRenderer = new AVFVideoFrameRenderer(m_surface, this); +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + if (m_playerLayer) { + m_frameRenderer->setPlayerLayer(static_cast<AVPlayerLayer*>(m_playerLayer)); + } +#endif + + auto checkHandleType = [this] { + m_enableOpenGL = m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).contains(QVideoFrame::Format_BGR32); + m_enableMetal = m_surface->supportedPixelFormats(QAbstractVideoBuffer::MTLTextureHandle).contains(QVideoFrame::Format_BGR32); + }; + checkHandleType(); + connect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged, this, checkHandleType); + + //If we already have a layer, but changed surfaces start rendering again + if (m_playerLayer && !m_displayLink->isActive()) { + m_displayLink->start(); + } + +} + +void AVFVideoRendererControl::setLayer(void *playerLayer) +{ + if (m_playerLayer == playerLayer) + return; + + [static_cast<AVPlayerLayer*>(m_playerLayer) release]; + + m_playerLayer = [static_cast<AVPlayerLayer*>(playerLayer) retain]; + + //If there is an active surface, make sure it has been stopped so that + //we can update it's state with the new content. + if (m_surface && m_surface->isActive()) + m_surface->stop(); + +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + if (m_frameRenderer) { + m_frameRenderer->setPlayerLayer(static_cast<AVPlayerLayer*>(playerLayer)); + } +#endif + + //If there is no layer to render, stop scheduling updates + if (m_playerLayer == nullptr) { + m_displayLink->stop(); + return; + } + + setupVideoOutput(); + + //If we now have both a valid surface and layer, start scheduling updates + if (m_surface && !m_displayLink->isActive()) { + m_displayLink->start(); + } +} + +void AVFVideoRendererControl::updateVideoFrame(const CVTimeStamp &ts) +{ + Q_UNUSED(ts); + + AVPlayerLayer *playerLayer = static_cast<AVPlayerLayer*>(m_playerLayer); + + if (!playerLayer) { + qWarning("updateVideoFrame called without AVPlayerLayer (which shouldn't happen"); + return; + } + + if (!playerLayer.readyForDisplay || !m_surface) + return; + + if (m_enableMetal) { + quint64 tex = m_frameRenderer->renderLayerToMTLTexture(playerLayer); + if (tex == 0) + return; + + auto buffer = new TextureVideoBuffer(QAbstractVideoBuffer::MTLTextureHandle, tex); + QVideoFrame frame(buffer, m_nativeSize, QVideoFrame::Format_BGR32); + if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat()) + m_surface->stop(); + + if (!m_surface->isActive()) { + QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::MTLTextureHandle); +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + format.setScanLineDirection(QVideoSurfaceFormat::TopToBottom); +#else + format.setScanLineDirection(QVideoSurfaceFormat::BottomToTop); +#endif + if (!m_surface->start(format)) + qWarning("Failed to activate video surface"); + } + + if (m_surface->isActive()) + m_surface->present(frame); + + return; + } + + if (m_enableOpenGL) { + quint64 tex = m_frameRenderer->renderLayerToTexture(playerLayer); + //Make sure we got a valid texture + if (tex == 0) + return; + + QAbstractVideoBuffer *buffer = new TextureVideoBuffer(QAbstractVideoBuffer::GLTextureHandle, tex); + QVideoFrame frame = QVideoFrame(buffer, m_nativeSize, QVideoFrame::Format_BGR32); + + if (m_surface && frame.isValid()) { + if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat()) + m_surface->stop(); + + if (!m_surface->isActive()) { + QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::GLTextureHandle); +#if defined(Q_OS_IOS) || defined(Q_OS_TVOS) + format.setScanLineDirection(QVideoSurfaceFormat::TopToBottom); +#else + format.setScanLineDirection(QVideoSurfaceFormat::BottomToTop); +#endif + if (!m_surface->start(format)) { + //Surface doesn't support GLTextureHandle + qWarning("Failed to activate video surface"); + } + } + + if (m_surface->isActive()) + m_surface->present(frame); + } + } else { + //fallback to rendering frames to QImages + QImage frameData = m_frameRenderer->renderLayerToImage(playerLayer); + + if (frameData.isNull()) { + return; + } + + QAbstractVideoBuffer *buffer = new QImageVideoBuffer(frameData); + QVideoFrame frame = QVideoFrame(buffer, m_nativeSize, QVideoFrame::Format_ARGB32); + if (m_surface && frame.isValid()) { + if (m_surface->isActive() && m_surface->surfaceFormat().pixelFormat() != frame.pixelFormat()) + m_surface->stop(); + + if (!m_surface->isActive()) { + QVideoSurfaceFormat format(frame.size(), frame.pixelFormat(), QAbstractVideoBuffer::NoHandle); + + if (!m_surface->start(format)) { + qWarning("Failed to activate video surface"); + } + } + + if (m_surface->isActive()) + m_surface->present(frame); + } + + } +} + +void AVFVideoRendererControl::setupVideoOutput() +{ + AVPlayerLayer *playerLayer = static_cast<AVPlayerLayer*>(m_playerLayer); + if (playerLayer) + m_nativeSize = QSize(playerLayer.bounds.size.width, playerLayer.bounds.size.height); +} diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h new file mode 100644 index 000000000..e46444745 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideorenderercontrol_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 AVFVIDEORENDERERCONTROL_H +#define AVFVIDEORENDERERCONTROL_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 <QtMultimedia/QVideoRendererControl> +#include <QtCore/QMutex> +#include <QtCore/QSize> + +#include "avfvideooutput_p.h" + +#import <CoreVideo/CVBase.h> + +QT_BEGIN_NAMESPACE + +class AVFDisplayLink; +class AVFVideoFrameRenderer; + +class AVFVideoRendererControl : public QVideoRendererControl, public AVFVideoOutput +{ + Q_OBJECT + Q_INTERFACES(AVFVideoOutput) +public: + explicit AVFVideoRendererControl(QObject *parent = nullptr); + virtual ~AVFVideoRendererControl(); + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + + void setLayer(void *playerLayer) override; + +private Q_SLOTS: + void updateVideoFrame(const CVTimeStamp &ts); + +Q_SIGNALS: + void surfaceChanged(QAbstractVideoSurface *surface); + +private: + void setupVideoOutput(); + + QMutex m_mutex; + QAbstractVideoSurface *m_surface; + + void *m_playerLayer; + + AVFVideoFrameRenderer *m_frameRenderer; + AVFDisplayLink *m_displayLink; + QSize m_nativeSize; + bool m_enableOpenGL; + bool m_enableMetal; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEORENDERERCONTROL_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm new file mode 100644 index 000000000..85eb82dcb --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol.mm @@ -0,0 +1,255 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). +** 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 "avfvideowindowcontrol_p.h" + +#include <AVFoundation/AVFoundation.h> +#import <QuartzCore/CATransaction.h> + +#if QT_HAS_INCLUDE(<AppKit/AppKit.h>) +#include <AppKit/AppKit.h> +#endif + +#if QT_HAS_INCLUDE(<UIKit/UIKit.h>) +#include <UIKit/UIKit.h> +#endif + +QT_USE_NAMESPACE + +AVFVideoWindowControl::AVFVideoWindowControl(QObject *parent) + : QVideoWindowControl(parent) + , m_winId(0) + , m_fullscreen(false) + , m_brightness(0) + , m_contrast(0) + , m_hue(0) + , m_saturation(0) + , m_aspectRatioMode(Qt::IgnoreAspectRatio) + , m_playerLayer(nullptr) + , m_nativeView(nullptr) +{ +} + +AVFVideoWindowControl::~AVFVideoWindowControl() +{ + if (m_playerLayer) { + [m_playerLayer removeFromSuperlayer]; + [m_playerLayer release]; + } +} + +WId AVFVideoWindowControl::winId() const +{ + return m_winId; +} + +void AVFVideoWindowControl::setWinId(WId id) +{ + m_winId = id; + m_nativeView = (NativeView*)m_winId; +} + +QRect AVFVideoWindowControl::displayRect() const +{ + return m_displayRect; +} + +void AVFVideoWindowControl::setDisplayRect(const QRect &rect) +{ + if (m_displayRect != rect) { + m_displayRect = rect; + updatePlayerLayerBounds(); + } +} + +bool AVFVideoWindowControl::isFullScreen() const +{ + return m_fullscreen; +} + +void AVFVideoWindowControl::setFullScreen(bool fullScreen) +{ + if (m_fullscreen != fullScreen) { + m_fullscreen = fullScreen; + Q_EMIT QVideoWindowControl::fullScreenChanged(fullScreen); + } +} + +void AVFVideoWindowControl::repaint() +{ + if (m_playerLayer) + [m_playerLayer setNeedsDisplay]; +} + +QSize AVFVideoWindowControl::nativeSize() const +{ + return m_nativeSize; +} + +Qt::AspectRatioMode AVFVideoWindowControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void AVFVideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + if (m_aspectRatioMode != mode) { + m_aspectRatioMode = mode; + updateAspectRatio(); + } +} + +int AVFVideoWindowControl::brightness() const +{ + return m_brightness; +} + +void AVFVideoWindowControl::setBrightness(int brightness) +{ + if (m_brightness != brightness) { + m_brightness = brightness; + Q_EMIT QVideoWindowControl::brightnessChanged(brightness); + } +} + +int AVFVideoWindowControl::contrast() const +{ + return m_contrast; +} + +void AVFVideoWindowControl::setContrast(int contrast) +{ + if (m_contrast != contrast) { + m_contrast = contrast; + Q_EMIT QVideoWindowControl::contrastChanged(contrast); + } +} + +int AVFVideoWindowControl::hue() const +{ + return m_hue; +} + +void AVFVideoWindowControl::setHue(int hue) +{ + if (m_hue != hue) { + m_hue = hue; + Q_EMIT QVideoWindowControl::hueChanged(hue); + } +} + +int AVFVideoWindowControl::saturation() const +{ + return m_saturation; +} + +void AVFVideoWindowControl::setSaturation(int saturation) +{ + if (m_saturation != saturation) { + m_saturation = saturation; + Q_EMIT QVideoWindowControl::saturationChanged(saturation); + } +} + +void AVFVideoWindowControl::setLayer(void *playerLayer) +{ + AVPlayerLayer *layer = static_cast<AVPlayerLayer*>(playerLayer); + if (m_playerLayer == layer) + return; + + if (!m_winId) { + qDebug("AVFVideoWindowControl: No video window"); + return; + } + +#if defined(Q_OS_OSX) + [m_nativeView setWantsLayer:YES]; +#endif + + if (m_playerLayer) { + [m_playerLayer removeFromSuperlayer]; + [m_playerLayer release]; + } + + m_playerLayer = layer; + + CALayer *nativeLayer = [m_nativeView layer]; + + if (layer) { + [layer retain]; + + m_nativeSize = QSize(m_playerLayer.bounds.size.width, + m_playerLayer.bounds.size.height); + + updateAspectRatio(); + [nativeLayer addSublayer:m_playerLayer]; + updatePlayerLayerBounds(); + } +} + +void AVFVideoWindowControl::updateAspectRatio() +{ + if (m_playerLayer) { + switch (m_aspectRatioMode) { + case Qt::IgnoreAspectRatio: + [m_playerLayer setVideoGravity:AVLayerVideoGravityResize]; + break; + case Qt::KeepAspectRatio: + [m_playerLayer setVideoGravity:AVLayerVideoGravityResizeAspect]; + break; + case Qt::KeepAspectRatioByExpanding: + [m_playerLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill]; + break; + default: + break; + } + } +} + +void AVFVideoWindowControl::updatePlayerLayerBounds() +{ + if (m_playerLayer) { + [CATransaction begin]; + [CATransaction setDisableActions: YES]; // disable animation/flicks + m_playerLayer.frame = m_displayRect.toCGRect(); + [CATransaction commit]; + } +} + +#include "moc_avfvideowindowcontrol_p.cpp" diff --git a/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h new file mode 100644 index 000000000..f4f2fc580 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/avfvideowindowcontrol_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** 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 AVFVIDEOWINDOWCONTROL_H +#define AVFVIDEOWINDOWCONTROL_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 <QVideoWindowControl> + +@class AVPlayerLayer; +#if defined(Q_OS_OSX) +@class NSView; +typedef NSView NativeView; +#else +@class UIView; +typedef UIView NativeView; +#endif + +#include "avfvideooutput_p.h" + +QT_BEGIN_NAMESPACE + +class AVFVideoWindowControl : public QVideoWindowControl, public AVFVideoOutput +{ + Q_OBJECT + Q_INTERFACES(AVFVideoOutput) + +public: + AVFVideoWindowControl(QObject *parent = nullptr); + virtual ~AVFVideoWindowControl(); + + // QVideoWindowControl interface +public: + WId winId() const override; + void setWinId(WId id) override; + + QRect displayRect() const override; + void setDisplayRect(const QRect &rect) override; + + bool isFullScreen() const override; + void setFullScreen(bool fullScreen) override; + + void repaint() override; + QSize nativeSize() const override; + + Qt::AspectRatioMode aspectRatioMode() const override; + void setAspectRatioMode(Qt::AspectRatioMode mode) override; + + int brightness() const override; + void setBrightness(int brightness) override; + + int contrast() const override; + void setContrast(int contrast) override; + + int hue() const override; + void setHue(int hue) override; + + int saturation() const override; + void setSaturation(int saturation) override; + + // AVFVideoOutput interface + void setLayer(void *playerLayer) override; + +private: + void updateAspectRatio(); + void updatePlayerLayerBounds(); + + WId m_winId; + QRect m_displayRect; + bool m_fullscreen; + int m_brightness; + int m_contrast; + int m_hue; + int m_saturation; + Qt::AspectRatioMode m_aspectRatioMode; + QSize m_nativeSize; + AVPlayerLayer *m_playerLayer; + NativeView *m_nativeView; +}; + +QT_END_NAMESPACE + +#endif // AVFVIDEOWINDOWCONTROL_H diff --git a/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri b/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..2929ffe50 --- /dev/null +++ b/src/multimedia/platform/avfoundation/mediaplayer/mediaplayer.pri @@ -0,0 +1,48 @@ +QT += opengl network + +HEADERS += \ + $$PWD/avfmediaplayercontrol_p.h \ + $$PWD/avfmediaplayermetadatacontrol_p.h \ + $$PWD/avfmediaplayerservice_p.h \ + $$PWD/avfmediaplayersession_p.h \ + $$PWD/avfmediaplayerserviceplugin_p.h \ + $$PWD/avfvideooutput_p.h \ + $$PWD/avfvideowindowcontrol_p.h + +SOURCES += \ + $$PWD/avfmediaplayercontrol.mm \ + $$PWD/avfmediaplayermetadatacontrol.mm \ + $$PWD/avfmediaplayerservice.mm \ + $$PWD/avfmediaplayerserviceplugin.mm \ + $$PWD/avfmediaplayersession.mm \ + $$PWD/avfvideooutput.mm \ + $$PWD/avfvideowindowcontrol.mm + +ios|tvos { + qtConfig(opengl) { + HEADERS += \ + $$PWD/avfvideoframerenderer_ios_p.h \ + $$PWD/avfvideorenderercontrol_p.h \ + $$PWD/avfdisplaylink_p.h + + SOURCES += \ + $$PWD/avfvideoframerenderer_ios.mm \ + $$PWD/avfvideorenderercontrol.mm \ + $$PWD/avfdisplaylink.mm + } + LIBS += -framework Foundation +} else { + LIBS += -framework AppKit + + qtConfig(opengl) { + HEADERS += \ + $$PWD/avfvideoframerenderer_p.h \ + $$PWD/avfvideorenderercontrol_p.h \ + $$PWD/avfdisplaylink_p.h + + SOURCES += \ + $$PWD/avfvideoframerenderer.mm \ + $$PWD/avfvideorenderercontrol.mm \ + $$PWD/avfdisplaylink.mm + } +} diff --git a/src/multimedia/platform/coreaudio/coreaudio.pri b/src/multimedia/platform/coreaudio/coreaudio.pri index 538e9b4a9..1ee6cf7c8 100644 --- a/src/multimedia/platform/coreaudio/coreaudio.pri +++ b/src/multimedia/platform/coreaudio/coreaudio.pri @@ -1,20 +1,20 @@ HEADERS += \ - platform/coreaudio/qcoreaudiodeviceinfo_p.h \ - platform/coreaudio/qcoreaudioinput_p.h \ - platform/coreaudio/qcoreaudiooutput_p.h \ - platform/coreaudio/qcoreaudiointerface_p.h \ - platform/coreaudio/qcoreaudioutils_p.h + $$PWD/qcoreaudiodeviceinfo_p.h \ + $$PWD/qcoreaudioinput_p.h \ + $$PWD/qcoreaudiooutput_p.h \ + $$PWD/qcoreaudiointerface_p.h \ + $$PWD/qcoreaudioutils_p.h SOURCES += \ - platform/coreaudio/qcoreaudiodeviceinfo.mm \ - platform/coreaudio/qcoreaudioinput.mm \ - platform/coreaudio/qcoreaudiooutput.mm \ - platform/coreaudio/qcoreaudiointerface.mm \ - platform/coreaudio/qcoreaudioutils.mm + $$PWD/qcoreaudiodeviceinfo.mm \ + $$PWD/qcoreaudioinput.mm \ + $$PWD/qcoreaudiooutput.mm \ + $$PWD/qcoreaudiointerface.mm \ + $$PWD/qcoreaudioutils.mm ios|tvos { - HEADERS += platform/coreaudio/qcoreaudiosessionmanager_p.h - SOURCES += platform/coreaudio/qcoreaudiosessionmanager.mm + HEADERS += $$PWD/qcoreaudiosessionmanager_p.h + SOURCES += $$PWD/qcoreaudiosessionmanager.mm LIBS += -framework Foundation -framework AVFoundation } else { LIBS += \ diff --git a/src/multimedia/platform/gstreamer/audio/audio.pri b/src/multimedia/platform/gstreamer/audio/audio.pri new file mode 100644 index 000000000..9cf3d9e68 --- /dev/null +++ b/src/multimedia/platform/gstreamer/audio/audio.pri @@ -0,0 +1,15 @@ +HEADERS += \ + $$PWD/qaudiointerface_gstreamer_p.h \ + $$PWD/qaudiodeviceinfo_gstreamer_p.h \ + $$PWD/qaudiooutput_gstreamer_p.h \ + $$PWD/qaudioinput_gstreamer_p.h \ + $$PWD/qaudioengine_gstreamer_p.h \ + $$PWD/qgstreameraudiodecodercontrol_p.h + +SOURCES += \ + $$PWD/qaudiointerface_gstreamer.cpp \ + $$PWD/qaudiodeviceinfo_gstreamer.cpp \ + $$PWD/qaudiooutput_gstreamer.cpp \ + $$PWD/qaudioinput_gstreamer.cpp \ + $$PWD/qaudioengine_gstreamer.cpp \ + $$PWD/qgstreameraudiodecodercontrol.cpp diff --git a/src/multimedia/platform/gstreamer/qaudiodeviceinfo_gstreamer.cpp b/src/multimedia/platform/gstreamer/audio/qaudiodeviceinfo_gstreamer.cpp index ace067540..ace067540 100644 --- a/src/multimedia/platform/gstreamer/qaudiodeviceinfo_gstreamer.cpp +++ b/src/multimedia/platform/gstreamer/audio/qaudiodeviceinfo_gstreamer.cpp diff --git a/src/multimedia/platform/gstreamer/qaudiodeviceinfo_gstreamer_p.h b/src/multimedia/platform/gstreamer/audio/qaudiodeviceinfo_gstreamer_p.h index 85c7a2c92..85c7a2c92 100644 --- a/src/multimedia/platform/gstreamer/qaudiodeviceinfo_gstreamer_p.h +++ b/src/multimedia/platform/gstreamer/audio/qaudiodeviceinfo_gstreamer_p.h diff --git a/src/multimedia/platform/gstreamer/qaudioengine_gstreamer.cpp b/src/multimedia/platform/gstreamer/audio/qaudioengine_gstreamer.cpp index 1108fa7b5..1108fa7b5 100644 --- a/src/multimedia/platform/gstreamer/qaudioengine_gstreamer.cpp +++ b/src/multimedia/platform/gstreamer/audio/qaudioengine_gstreamer.cpp diff --git a/src/multimedia/platform/gstreamer/qaudioengine_gstreamer_p.h b/src/multimedia/platform/gstreamer/audio/qaudioengine_gstreamer_p.h index e7043c3f8..e7043c3f8 100644 --- a/src/multimedia/platform/gstreamer/qaudioengine_gstreamer_p.h +++ b/src/multimedia/platform/gstreamer/audio/qaudioengine_gstreamer_p.h diff --git a/src/multimedia/platform/gstreamer/qaudioinput_gstreamer.cpp b/src/multimedia/platform/gstreamer/audio/qaudioinput_gstreamer.cpp index 60b2a6b83..60b2a6b83 100644 --- a/src/multimedia/platform/gstreamer/qaudioinput_gstreamer.cpp +++ b/src/multimedia/platform/gstreamer/audio/qaudioinput_gstreamer.cpp diff --git a/src/multimedia/platform/gstreamer/qaudioinput_gstreamer_p.h b/src/multimedia/platform/gstreamer/audio/qaudioinput_gstreamer_p.h index ceff047c0..ceff047c0 100644 --- a/src/multimedia/platform/gstreamer/qaudioinput_gstreamer_p.h +++ b/src/multimedia/platform/gstreamer/audio/qaudioinput_gstreamer_p.h diff --git a/src/multimedia/platform/gstreamer/qaudiointerface_gstreamer.cpp b/src/multimedia/platform/gstreamer/audio/qaudiointerface_gstreamer.cpp index b449a989a..b449a989a 100644 --- a/src/multimedia/platform/gstreamer/qaudiointerface_gstreamer.cpp +++ b/src/multimedia/platform/gstreamer/audio/qaudiointerface_gstreamer.cpp diff --git a/src/multimedia/platform/gstreamer/qaudiointerface_gstreamer_p.h b/src/multimedia/platform/gstreamer/audio/qaudiointerface_gstreamer_p.h index 7a79bd186..7a79bd186 100644 --- a/src/multimedia/platform/gstreamer/qaudiointerface_gstreamer_p.h +++ b/src/multimedia/platform/gstreamer/audio/qaudiointerface_gstreamer_p.h diff --git a/src/multimedia/platform/gstreamer/qaudiooutput_gstreamer.cpp b/src/multimedia/platform/gstreamer/audio/qaudiooutput_gstreamer.cpp index eaa4c52f2..eaa4c52f2 100644 --- a/src/multimedia/platform/gstreamer/qaudiooutput_gstreamer.cpp +++ b/src/multimedia/platform/gstreamer/audio/qaudiooutput_gstreamer.cpp diff --git a/src/multimedia/platform/gstreamer/qaudiooutput_gstreamer_p.h b/src/multimedia/platform/gstreamer/audio/qaudiooutput_gstreamer_p.h index 685685b61..685685b61 100644 --- a/src/multimedia/platform/gstreamer/qaudiooutput_gstreamer_p.h +++ b/src/multimedia/platform/gstreamer/audio/qaudiooutput_gstreamer_p.h diff --git a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol.cpp b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol.cpp new file mode 100644 index 000000000..73cfe02b5 --- /dev/null +++ b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol.cpp @@ -0,0 +1,599 @@ +/**************************************************************************** +** +** Copyright (C) 2020 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$ +** +****************************************************************************/ +//#define DEBUG_DECODER + +#include "qgstreameraudiodecodercontrol_p.h" +#include <private/qgstreamerbushelper_p.h> + +#include <private/qgstutils_p.h> + +#include <gst/gstvalue.h> +#include <gst/base/gstbasesrc.h> + +#include <QtCore/qdatetime.h> +#include <QtCore/qdebug.h> +#include <QtCore/qsize.h> +#include <QtCore/qtimer.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtCore/qstandardpaths.h> +#include <QtCore/qurl.h> + +#define MAX_BUFFERS_IN_QUEUE 4 + +QT_BEGIN_NAMESPACE + +typedef enum { + GST_PLAY_FLAG_VIDEO = 0x00000001, + GST_PLAY_FLAG_AUDIO = 0x00000002, + GST_PLAY_FLAG_TEXT = 0x00000004, + GST_PLAY_FLAG_VIS = 0x00000008, + GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010, + GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020, + GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040, + GST_PLAY_FLAG_DOWNLOAD = 0x00000080, + GST_PLAY_FLAG_BUFFERING = 0x000000100 +} GstPlayFlags; + +QGstreamerAudioDecoderControl::QGstreamerAudioDecoderControl(QObject *parent) + : QAudioDecoderControl(parent), + m_state(QAudioDecoder::StoppedState), + m_pendingState(QAudioDecoder::StoppedState), + m_busHelper(0), + m_bus(0), + m_playbin(0), + m_outputBin(0), + m_audioConvert(0), + m_appSink(0), +#if QT_CONFIG(gstreamer_app) + m_appSrc(0), +#endif + mDevice(0), + m_buffersAvailable(0), + m_position(-1), + m_duration(-1), + m_durationQueries(0) +{ + // Create pipeline here + m_playbin = gst_element_factory_make("playbin", NULL); + + if (m_playbin != 0) { + // Sort out messages + m_bus = gst_element_get_bus(m_playbin); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installMessageFilter(this); + + // Set the rest of the pipeline up + setAudioFlags(true); + + m_audioConvert = gst_element_factory_make("audioconvert", NULL); + + m_outputBin = gst_bin_new("audio-output-bin"); + gst_bin_add(GST_BIN(m_outputBin), m_audioConvert); + + // add ghostpad + GstPad *pad = gst_element_get_static_pad(m_audioConvert, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(m_outputBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + g_object_set(G_OBJECT(m_playbin), "audio-sink", m_outputBin, NULL); +#if QT_CONFIG(gstreamer_app) + g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderControl::configureAppSrcElement, (gpointer)this); +#endif + + // Set volume to 100% + gdouble volume = 1.0; + g_object_set(G_OBJECT(m_playbin), "volume", volume, NULL); + } +} + +QGstreamerAudioDecoderControl::~QGstreamerAudioDecoderControl() +{ + if (m_playbin) { + stop(); + + delete m_busHelper; +#if QT_CONFIG(gstreamer_app) + delete m_appSrc; +#endif + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_playbin)); + } +} + +#if QT_CONFIG(gstreamer_app) +void QGstreamerAudioDecoderControl::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerAudioDecoderControl* self) +{ + Q_UNUSED(object); + Q_UNUSED(pspec); + + // In case we switch from appsrc to not + if (!self->appsrc()) + return; + + GstElement *appsrc; + g_object_get(orig, "source", &appsrc, NULL); + + if (!self->appsrc()->setup(appsrc)) + qWarning()<<"Could not setup appsrc element"; + + g_object_unref(G_OBJECT(appsrc)); +} +#endif + +bool QGstreamerAudioDecoderControl::processBusMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) { + updateDuration(); + } else if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_playbin)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_STATE_CHANGED: + { + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + +#ifdef DEBUG_DECODER + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]) << "internal" << m_state; +#endif + + QAudioDecoder::State prevState = m_state; + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + m_state = QAudioDecoder::StoppedState; + break; + case GST_STATE_READY: + m_state = QAudioDecoder::StoppedState; + break; + case GST_STATE_PLAYING: + m_state = QAudioDecoder::DecodingState; + break; + case GST_STATE_PAUSED: + m_state = QAudioDecoder::DecodingState; + + //gstreamer doesn't give a reliable indication the duration + //information is ready, GST_MESSAGE_DURATION is not sent by most elements + //the duration is queried up to 5 times with increasing delay + m_durationQueries = 5; + updateDuration(); + break; + } + + if (prevState != m_state) + emit stateChanged(m_state); + } + break; + + case GST_MESSAGE_EOS: + m_pendingState = m_state = QAudioDecoder::StoppedState; + emit finished(); + emit stateChanged(m_state); + break; + + case GST_MESSAGE_ERROR: { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) + processInvalidMedia(QAudioDecoder::FormatError, tr("Cannot play stream of type: <unknown>")); + else + processInvalidMedia(QAudioDecoder::ResourceError, QString::fromUtf8(err->message)); + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } + break; + case GST_MESSAGE_WARNING: + { + GError *err; + gchar *debug; + gst_message_parse_warning (gm, &err, &debug); + qWarning() << "Warning:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } + break; +#ifdef DEBUG_DECODER + case GST_MESSAGE_INFO: + { + GError *err; + gchar *debug; + gst_message_parse_info (gm, &err, &debug); + qDebug() << "Info:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } + break; +#endif + default: + break; + } + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + QAudioDecoder::Error qerror = QAudioDecoder::ResourceError; + if (err->domain == GST_STREAM_ERROR) { + switch (err->code) { + case GST_STREAM_ERROR_DECRYPT: + case GST_STREAM_ERROR_DECRYPT_NOKEY: + qerror = QAudioDecoder::AccessDeniedError; + break; + case GST_STREAM_ERROR_FORMAT: + case GST_STREAM_ERROR_DEMUX: + case GST_STREAM_ERROR_DECODE: + case GST_STREAM_ERROR_WRONG_TYPE: + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + qerror = QAudioDecoder::FormatError; + break; + default: + break; + } + } else if (err->domain == GST_CORE_ERROR) { + switch (err->code) { + case GST_CORE_ERROR_MISSING_PLUGIN: + qerror = QAudioDecoder::FormatError; + break; + default: + break; + } + } + + processInvalidMedia(qerror, QString::fromUtf8(err->message)); + g_error_free(err); + g_free(debug); + } + } + + return false; +} + +QString QGstreamerAudioDecoderControl::sourceFilename() const +{ + return mSource; +} + +void QGstreamerAudioDecoderControl::setSourceFilename(const QString &fileName) +{ + stop(); + mDevice = 0; +#if QT_CONFIG(gstreamer_app) + if (m_appSrc) + m_appSrc->deleteLater(); + m_appSrc = 0; +#endif + + bool isSignalRequired = (mSource != fileName); + mSource = fileName; + if (isSignalRequired) + emit sourceChanged(); +} + +QIODevice *QGstreamerAudioDecoderControl::sourceDevice() const +{ + return mDevice; +} + +void QGstreamerAudioDecoderControl::setSourceDevice(QIODevice *device) +{ + stop(); + mSource.clear(); + bool isSignalRequired = (mDevice != device); + mDevice = device; + if (isSignalRequired) + emit sourceChanged(); +} + +void QGstreamerAudioDecoderControl::start() +{ + if (!m_playbin) { + processInvalidMedia(QAudioDecoder::ResourceError, "Playbin element is not valid"); + return; + } + + addAppSink(); + + if (!mSource.isEmpty()) { + g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL); + } else if (mDevice) { +#if QT_CONFIG(gstreamer_app) + // make sure we can read from device + if (!mDevice->isOpen() || !mDevice->isReadable()) { + processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device"); + return; + } + + if (!m_appSrc) + m_appSrc = new QGstAppSrc(this); + m_appSrc->setStream(mDevice); + + g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL); +#endif + } else { + return; + } + + // Set audio format + if (m_appSink) { + if (mFormat.isValid()) { + setAudioFlags(false); + GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat); + gst_app_sink_set_caps(m_appSink, caps); + gst_caps_unref(caps); + } else { + // We want whatever the native audio format is + setAudioFlags(true); + gst_app_sink_set_caps(m_appSink, NULL); + } + } + + m_pendingState = QAudioDecoder::DecodingState; + if (gst_element_set_state(m_playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to start decoding process"; + m_pendingState = m_state = QAudioDecoder::StoppedState; + + emit stateChanged(m_state); + } +} + +void QGstreamerAudioDecoderControl::stop() +{ + if (m_playbin) { + gst_element_set_state(m_playbin, GST_STATE_NULL); + removeAppSink(); + + QAudioDecoder::State oldState = m_state; + m_pendingState = m_state = QAudioDecoder::StoppedState; + + // GStreamer thread is stopped. Can safely access m_buffersAvailable + if (m_buffersAvailable != 0) { + m_buffersAvailable = 0; + emit bufferAvailableChanged(false); + } + + if (m_position != -1) { + m_position = -1; + emit positionChanged(m_position); + } + + if (m_duration != -1) { + m_duration = -1; + emit durationChanged(m_duration); + } + + if (oldState != m_state) + emit stateChanged(m_state); + } +} + +QAudioFormat QGstreamerAudioDecoderControl::audioFormat() const +{ + return mFormat; +} + +void QGstreamerAudioDecoderControl::setAudioFormat(const QAudioFormat &format) +{ + if (mFormat != format) { + mFormat = format; + emit formatChanged(mFormat); + } +} + +QAudioBuffer QGstreamerAudioDecoderControl::read() +{ + QAudioBuffer audioBuffer; + + int buffersAvailable; + { + QMutexLocker locker(&m_buffersMutex); + buffersAvailable = m_buffersAvailable; + + // need to decrement before pulling a buffer + // to make sure assert in QGstreamerAudioDecoderControl::new_buffer works + m_buffersAvailable--; + } + + + if (buffersAvailable) { + if (buffersAvailable == 1) + emit bufferAvailableChanged(false); + + const char* bufferData = 0; + int bufferSize = 0; + + GstSample *sample = gst_app_sink_pull_sample(m_appSink); + GstBuffer *buffer = gst_sample_get_buffer(sample); + GstMapInfo mapInfo; + gst_buffer_map(buffer, &mapInfo, GST_MAP_READ); + bufferData = (const char*)mapInfo.data; + bufferSize = mapInfo.size; + QAudioFormat format = QGstUtils::audioFormatForSample(sample); + + if (format.isValid()) { + // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer. + // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer. + qint64 position = getPositionFromBuffer(buffer); + audioBuffer = QAudioBuffer(QByteArray((const char*)bufferData, bufferSize), format, position); + position /= 1000; // convert to milliseconds + if (position != m_position) { + m_position = position; + emit positionChanged(m_position); + } + } + gst_buffer_unmap(buffer, &mapInfo); + gst_sample_unref(sample); + } + + return audioBuffer; +} + +bool QGstreamerAudioDecoderControl::bufferAvailable() const +{ + QMutexLocker locker(&m_buffersMutex); + return m_buffersAvailable > 0; +} + +qint64 QGstreamerAudioDecoderControl::position() const +{ + return m_position; +} + +qint64 QGstreamerAudioDecoderControl::duration() const +{ + return m_duration; +} + +void QGstreamerAudioDecoderControl::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString) +{ + stop(); + emit error(int(errorCode), errorString); +} + +GstFlowReturn QGstreamerAudioDecoderControl::new_sample(GstAppSink *, gpointer user_data) +{ + // "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()." + QGstreamerAudioDecoderControl *control = reinterpret_cast<QGstreamerAudioDecoderControl*>(user_data); + + int buffersAvailable; + { + QMutexLocker locker(&control->m_buffersMutex); + buffersAvailable = control->m_buffersAvailable; + control->m_buffersAvailable++; + Q_ASSERT(control->m_buffersAvailable <= MAX_BUFFERS_IN_QUEUE); + } + + if (!buffersAvailable) + QMetaObject::invokeMethod(control, "bufferAvailableChanged", Qt::QueuedConnection, Q_ARG(bool, true)); + QMetaObject::invokeMethod(control, "bufferReady", Qt::QueuedConnection); + return GST_FLOW_OK; +} + +void QGstreamerAudioDecoderControl::setAudioFlags(bool wantNativeAudio) +{ + int flags = 0; + if (m_playbin) { + g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL); + // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired + // it prevents audio format conversion + flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO); + flags |= GST_PLAY_FLAG_AUDIO; + if (wantNativeAudio) + flags |= GST_PLAY_FLAG_NATIVE_AUDIO; + g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL); + } +} + +void QGstreamerAudioDecoderControl::addAppSink() +{ + if (m_appSink) + return; + + m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL); + + GstAppSinkCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.new_sample = &new_sample; + gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL); + gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE); + gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE); + + gst_bin_add(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); + gst_element_link(m_audioConvert, GST_ELEMENT(m_appSink)); +} + +void QGstreamerAudioDecoderControl::removeAppSink() +{ + if (!m_appSink) + return; + + gst_element_unlink(m_audioConvert, GST_ELEMENT(m_appSink)); + gst_bin_remove(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); + + m_appSink = 0; +} + +void QGstreamerAudioDecoderControl::updateDuration() +{ + gint64 gstDuration = 0; + int duration = -1; + + if (m_playbin && qt_gst_element_query_duration(m_playbin, GST_FORMAT_TIME, &gstDuration)) + duration = gstDuration / 1000000; + + if (m_duration != duration) { + m_duration = duration; + emit durationChanged(m_duration); + } + + if (m_duration > 0) + m_durationQueries = 0; + + if (m_durationQueries > 0) { + //increase delay between duration requests + int delay = 25 << (5 - m_durationQueries); + QTimer::singleShot(delay, this, SLOT(updateDuration())); + m_durationQueries--; + } +} + +qint64 QGstreamerAudioDecoderControl::getPositionFromBuffer(GstBuffer* buffer) +{ + qint64 position = GST_BUFFER_TIMESTAMP(buffer); + if (position >= 0) + position = position / G_GINT64_CONSTANT(1000); // microseconds + else + position = -1; + return position; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol_p.h b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol_p.h new file mode 100644 index 000000000..6f31ad261 --- /dev/null +++ b/src/multimedia/platform/gstreamer/audio/qgstreameraudiodecodercontrol_p.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** 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 QGSTREAMERAUDIODECODERCONTROL_H +#define QGSTREAMERAUDIODECODERCONTROL_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QObject> +#include <QtCore/qmutex.h> +#include "qaudiodecodercontrol.h" +#include <private/qgstreamerbushelper_p.h> +#include "qaudiodecoder.h" + +#if QT_CONFIG(gstreamer_app) +#include <private/qgstappsrc_p.h> +#endif + +#include <gst/gst.h> +#include <gst/app/gstappsink.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerBusHelper; +class QGstreamerMessage; + +class QGstreamerAudioDecoderControl + : public QAudioDecoderControl, + public QGstreamerBusMessageFilter +{ +Q_OBJECT +Q_INTERFACES(QGstreamerBusMessageFilter) + +public: + QGstreamerAudioDecoderControl(QObject *parent); + virtual ~QGstreamerAudioDecoderControl(); + + // QAudioDecoder interface + QAudioDecoder::State state() const override { return m_state; } + + QString sourceFilename() const override; + void setSourceFilename(const QString &fileName) override; + + QIODevice *sourceDevice() const override; + void setSourceDevice(QIODevice *device) override; + + void start() override; + void stop() override; + + QAudioFormat audioFormat() const override; + void setAudioFormat(const QAudioFormat &format) override; + + QAudioBuffer read() override; + bool bufferAvailable() const override; + + qint64 position() const override; + qint64 duration() const override; + + // GStreamerBusMessageFilter interface + bool processBusMessage(const QGstreamerMessage &message) override; + + QGstreamerBusHelper *bus() const { return m_busHelper; } + QAudioDecoder::State pendingState() const { return m_pendingState; } + +#if QT_CONFIG(gstreamer_app) + QGstAppSrc *appsrc() const { return m_appSrc; } + static void configureAppSrcElement(GObject*, GObject*, GParamSpec*, QGstreamerAudioDecoderControl *_this); +#endif + + static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data); + +private slots: + void updateDuration(); + +private: + void setAudioFlags(bool wantNativeAudio); + void addAppSink(); + void removeAppSink(); + + void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString); + static qint64 getPositionFromBuffer(GstBuffer* buffer); + + QAudioDecoder::State m_state; + QAudioDecoder::State m_pendingState; + QGstreamerBusHelper *m_busHelper; + GstBus *m_bus; + GstElement *m_playbin; + GstElement *m_outputBin; + GstElement *m_audioConvert; + GstAppSink *m_appSink; + +#if QT_CONFIG(gstreamer_app) + QGstAppSrc *m_appSrc; +#endif + + QString mSource; + QIODevice *mDevice; // QWeakPointer perhaps + QAudioFormat mFormat; + + mutable QMutex m_buffersMutex; + int m_buffersAvailable; + + qint64 m_position; + qint64 m_duration; + + int m_durationQueries; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERPLAYERSESSION_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabin.pri b/src/multimedia/platform/gstreamer/camerabin/camerabin.pri new file mode 100644 index 000000000..2cacd56b0 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabin.pri @@ -0,0 +1,51 @@ + +HEADERS += \ + $$PWD/camerabinserviceplugin_p.h \ + $$PWD/camerabinservice_p.h \ + $$PWD/camerabinsession_p.h \ + $$PWD/camerabincontrol_p.h \ + $$PWD/camerabinaudioencoder_p.h \ + $$PWD/camerabinimageencoder_p.h \ + $$PWD/camerabinrecorder_p.h \ + $$PWD/camerabincontainer_p.h \ + $$PWD/camerabinimagecapture_p.h \ + $$PWD/camerabinimageprocessing_p.h \ + $$PWD/camerabinmetadata_p.h \ + $$PWD/camerabinvideoencoder_p.h \ + +SOURCES += \ + $$PWD/camerabinserviceplugin.cpp \ + $$PWD/camerabinservice.cpp \ + $$PWD/camerabinsession.cpp \ + $$PWD/camerabincontrol.cpp \ + $$PWD/camerabinaudioencoder.cpp \ + $$PWD/camerabincontainer.cpp \ + $$PWD/camerabinimagecapture.cpp \ + $$PWD/camerabinimageencoder.cpp \ + $$PWD/camerabinimageprocessing.cpp \ + $$PWD/camerabinmetadata.cpp \ + $$PWD/camerabinrecorder.cpp \ + $$PWD/camerabinvideoencoder.cpp \ + +qtConfig(gstreamer__p.hotography) { + HEADERS += \ + $$PWD/camerabinfocus_p.h \ + $$PWD/camerabinexposure_p.h \ + + SOURCES += \ + $$PWD/camerabinexposure.cpp \ + $$PWD/camerabinfocus.cpp \ + + QMAKE_USE += gstreamer_photography + DEFINES += GST_USE_UNSTABLE_API #prevents warnings because of unstable _p.hotography API +} + +qtConfig(gstreamer_gl): QMAKE_USE += gstreamer_gl + +qtConfig(linux_v4l) { + HEADERS += \ + $$PWD/camerabinv4limageprocessing_p.h + + SOURCES += \ + $$PWD/camerabinv4limageprocessing.cpp +} diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder.cpp new file mode 100644 index 000000000..9a26016de --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabinaudioencoder.h" +#include "camerabincontainer.h" +#include <private/qgstutils_p.h> + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +CameraBinAudioEncoder::CameraBinAudioEncoder(QObject *parent) + :QAudioEncoderSettingsControl(parent) +#if QT_CONFIG(gstreamer_encodingprofiles) + , m_codecs(QGstCodecsInfo::AudioEncoder) +#endif +{ +} + +CameraBinAudioEncoder::~CameraBinAudioEncoder() +{ +} + +QStringList CameraBinAudioEncoder::supportedAudioCodecs() const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_codecs.supportedCodecs(); +#else + return QStringList(); +#endif +} + +QString CameraBinAudioEncoder::codecDescription(const QString &codecName) const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_codecs.codecDescription(codecName); +#else + Q_UNUSED(codecName); + return QString(); +#endif +} + +QList<int> CameraBinAudioEncoder::supportedSampleRates(const QAudioEncoderSettings &, bool *) const +{ + //TODO check element caps to find actual values + + return QList<int>(); +} + +QAudioEncoderSettings CameraBinAudioEncoder::audioSettings() const +{ + return m_audioSettings; +} + +void CameraBinAudioEncoder::setAudioSettings(const QAudioEncoderSettings &settings) +{ + if (m_audioSettings != settings) { + m_audioSettings = settings; + m_actualAudioSettings = settings; + emit settingsChanged(); + } +} + +QAudioEncoderSettings CameraBinAudioEncoder::actualAudioSettings() const +{ + return m_actualAudioSettings; +} + +void CameraBinAudioEncoder::setActualAudioSettings(const QAudioEncoderSettings &settings) +{ + m_actualAudioSettings = settings; +} + +void CameraBinAudioEncoder::resetActualSettings() +{ + m_actualAudioSettings = m_audioSettings; +} + +#if QT_CONFIG(gstreamer_encodingprofiles) + +GstEncodingProfile *CameraBinAudioEncoder::createProfile() +{ + QString codec = m_actualAudioSettings.codec(); + QString preset = m_actualAudioSettings.encodingOption(QStringLiteral("preset")).toString(); + GstCaps *caps; + + if (codec.isEmpty()) + return 0; + + caps = gst_caps_from_string(codec.toLatin1()); + + GstEncodingProfile *profile = (GstEncodingProfile *)gst_encoding_audio_profile_new( + caps, + !preset.isEmpty() ? preset.toLatin1().constData() : NULL, //preset + NULL, //restriction + 0); //presence + + gst_caps_unref(caps); + + return profile; +} + +#endif + +void CameraBinAudioEncoder::applySettings(GstElement *encoder) +{ + GObjectClass * const objectClass = G_OBJECT_GET_CLASS(encoder); + const char * const name = qt_gst_element_get_factory_name(encoder); + + const bool isVorbis = qstrcmp(name, "vorbisenc") == 0; + + const int bitRate = m_actualAudioSettings.bitRate(); + if (!isVorbis && bitRate == -1) { + // Bit rate is invalid, don't evaluate the remaining conditions unless the encoder is + // vorbisenc which is known to accept -1 as an unspecified bitrate. + } else if (g_object_class_find_property(objectClass, "bitrate")) { + g_object_set(G_OBJECT(encoder), "bitrate", bitRate, NULL); + } else if (g_object_class_find_property(objectClass, "target-bitrate")) { + g_object_set(G_OBJECT(encoder), "target-bitrate", bitRate, NULL); + } + + if (isVorbis) { + static const double qualities[] = { 0.1, 0.3, 0.5, 0.7, 1.0 }; + g_object_set(G_OBJECT(encoder), "quality", qualities[m_actualAudioSettings.quality()], NULL); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder_p.h new file mode 100644 index 000000000..0c6708449 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinaudioencoder_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 CAMERABINAUDIOENCODE_H +#define CAMERABINAUDIOENCODE_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qaudioencodersettingscontrol.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> + +#if QT_CONFIG(gstreamer_encodingprofiles) +#include <gst/pbutils/encoding-profile.h> +#include <private/qgstcodecsinfo_p.h> +#endif + +#include <qaudioformat.h> + +QT_BEGIN_NAMESPACE +class CameraBinSession; + +class CameraBinAudioEncoder : public QAudioEncoderSettingsControl +{ + Q_OBJECT +public: + CameraBinAudioEncoder(QObject *parent); + virtual ~CameraBinAudioEncoder(); + + QStringList supportedAudioCodecs() const override; + QString codecDescription(const QString &codecName) const override; + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings = QAudioEncoderSettings(), + bool *isContinuous = 0) const override; + QList<int> supportedChannelCounts(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + QList<int> supportedSampleSizes(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + + QAudioEncoderSettings audioSettings() const override; + void setAudioSettings(const QAudioEncoderSettings &) override; + + QAudioEncoderSettings actualAudioSettings() const; + void setActualAudioSettings(const QAudioEncoderSettings&); + void resetActualSettings(); + +#if QT_CONFIG(gstreamer_encodingprofiles) + GstEncodingProfile *createProfile(); +#endif + + void applySettings(GstElement *element); + +Q_SIGNALS: + void settingsChanged(); + +private: +#if QT_CONFIG(gstreamer_encodingprofiles) + QGstCodecsInfo m_codecs; +#endif + + QAudioEncoderSettings m_actualAudioSettings; + QAudioEncoderSettings m_audioSettings; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabincontainer.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabincontainer.cpp new file mode 100644 index 000000000..b19a5524e --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabincontainer.cpp @@ -0,0 +1,151 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabincontainer.h" +#include <private/qgstutils_p.h> + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +CameraBinContainer::CameraBinContainer(QObject *parent) + :QMediaContainerControl(parent) +#if QT_CONFIG(gstreamer_encodingprofiles) + , m_supportedContainers(QGstCodecsInfo::Muxer) +#endif +{ +} + +QStringList CameraBinContainer::supportedContainers() const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_supportedContainers.supportedCodecs(); +#else + return QStringList(); +#endif +} + +QString CameraBinContainer::containerDescription(const QString &formatMimeType) const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_supportedContainers.codecDescription(formatMimeType); +#else + Q_UNUSED(formatMimeType); + return QString(); +#endif +} + +QString CameraBinContainer::containerFormat() const +{ + return m_format; +} + +void CameraBinContainer::setContainerFormat(const QString &format) +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + if (m_format != format) { + m_format = format; + m_actualFormat = format; + emit settingsChanged(); + } +#endif +} + +QString CameraBinContainer::actualContainerFormat() const +{ + return m_actualFormat; +} + +void CameraBinContainer::setActualContainerFormat(const QString &containerFormat) +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + m_actualFormat = containerFormat; +#endif +} + +void CameraBinContainer::resetActualContainerFormat() +{ + m_actualFormat = m_format; +} + +#if QT_CONFIG(gstreamer_encodingprofiles) + +GstEncodingContainerProfile *CameraBinContainer::createProfile() +{ + GstCaps *caps = nullptr; + + if (m_actualFormat.isEmpty()) + return 0; + + QString format = m_actualFormat; + const QStringList supportedFormats = m_supportedContainers.supportedCodecs(); + + //if format is not in the list of supported gstreamer mime types, + //try to find the mime type with matching extension + if (!supportedFormats.contains(format)) { + format.clear(); + QString extension = QGstUtils::fileExtensionForMimeType(m_actualFormat); + for (const QString &formatCandidate : supportedFormats) { + if (QGstUtils::fileExtensionForMimeType(formatCandidate) == extension) { + format = formatCandidate; + break; + } + } + } + + if (format.isEmpty()) + return nullptr; + + caps = gst_caps_from_string(format.toLatin1()); + + GstEncodingContainerProfile *profile = (GstEncodingContainerProfile *)gst_encoding_container_profile_new( + "camerabin2_profile", + (gchar *)"custom camera profile", + caps, + NULL); //preset + + gst_caps_unref(caps); + + return profile; +} + +#endif + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabincontainer_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabincontainer_p.h new file mode 100644 index 000000000..c743adf83 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabincontainer_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 CAMERABINMEDIACONTAINERCONTROL_H +#define CAMERABINMEDIACONTAINERCONTROL_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qmediacontainercontrol.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> + +#if QT_CONFIG(gstreamer_encodingprofiles) +#include <gst/pbutils/encoding-profile.h> +#include <private/qgstcodecsinfo_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class CameraBinContainer : public QMediaContainerControl +{ +Q_OBJECT +public: + CameraBinContainer(QObject *parent); + virtual ~CameraBinContainer() {} + + QStringList supportedContainers() const override; + QString containerDescription(const QString &formatMimeType) const override; + + QString containerFormat() const override; + void setContainerFormat(const QString &format) override; + + QString actualContainerFormat() const; + void setActualContainerFormat(const QString &containerFormat); + void resetActualContainerFormat(); + +#if QT_CONFIG(gstreamer_encodingprofiles) + GstEncodingContainerProfile *createProfile(); +#endif + +Q_SIGNALS: + void settingsChanged(); + +private: + QString m_format; + QString m_actualFormat; + +#if QT_CONFIG(gstreamer_encodingprofiles) + QGstCodecsInfo m_supportedContainers; +#endif +}; + +QT_END_NAMESPACE + +#endif // CAMERABINMEDIACONTAINERCONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabincontrol.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabincontrol.cpp new file mode 100644 index 000000000..a02e9ab72 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabincontrol.cpp @@ -0,0 +1,432 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabincontrol.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" +#include "camerabinfocus.h" +#include "camerabinimageprocessing.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qcoreevent.h> + +QT_BEGIN_NAMESPACE + +//#define CAMEABIN_DEBUG 1 +#define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + +CameraBinControl::CameraBinControl(CameraBinSession *session) + :QCameraControl(session), + m_session(session), + m_state(QCamera::UnloadedState), + m_reloadPending(false), + m_focus(m_session->cameraFocusControl()) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), + this, SIGNAL(statusChanged(QCamera::Status))); + + connect(m_session, SIGNAL(viewfinderChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(readyChanged(bool)), + SLOT(reloadLater())); + connect(m_session, SIGNAL(error(int,QString)), + SLOT(handleCameraError(int,QString))); + + connect(m_session, SIGNAL(busyChanged(bool)), + SLOT(handleBusyChanged(bool))); + + connect(m_focus, SIGNAL(_q_focusStatusChanged(QCamera::LockStatus,QCamera::LockChangeReason)), + this, SLOT(updateFocusStatus(QCamera::LockStatus,QCamera::LockChangeReason))); +} + +CameraBinControl::~CameraBinControl() +{ +} + +QCamera::CaptureModes CameraBinControl::captureMode() const +{ + return m_session->captureMode(); +} + +void CameraBinControl::setCaptureMode(QCamera::CaptureModes mode) +{ + if (m_session->captureMode() != mode) { + m_session->setCaptureMode(mode); + + emit captureModeChanged(mode); + } +} + +bool CameraBinControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + return mode == QCamera::CaptureStillImage || mode == QCamera::CaptureVideo; +} + +void CameraBinControl::setState(QCamera::State state) +{ +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", state); +#endif + if (m_state != state) { + m_state = state; + + //special case for stopping the camera while it's busy, + //it should be delayed until the camera is idle + if ((state == QCamera::LoadedState || state == QCamera::UnloadedState) && + m_session->status() == QCamera::ActiveStatus && + m_session->isBusy()) { +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Camera is busy," + << state == QCamera::LoadedState ? "QCamera::stop()" : "QCamera::unload()" + << "is delayed"; +#endif + emit stateChanged(m_state); + return; + } + + //postpone changing to Active if the session is nor ready yet + if (state == QCamera::ActiveState) { + if (m_session->isReady()) { + m_session->setState(state); + } else { +#ifdef CAMEABIN_DEBUG + qDebug() << "Camera session is not ready yet, postpone activating"; +#endif + } + } else + m_session->setState(state); + + emit stateChanged(m_state); + } +} + +QCamera::State CameraBinControl::state() const +{ + return m_state; +} + +QCamera::Status CameraBinControl::status() const +{ + return m_session->status(); +} + +void CameraBinControl::reloadLater() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << "CameraBinControl: reload pipeline requested" << ENUM_NAME(QCamera, "State", m_state); +#endif + if (!m_reloadPending && m_state == QCamera::ActiveState) { + m_reloadPending = true; + + if (!m_session->isBusy()) { + m_session->setState(QCamera::LoadedState); + QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); + } + } +} + +void CameraBinControl::handleBusyChanged(bool busy) +{ + if (!busy && m_session->status() == QCamera::ActiveStatus) { + if (m_state != QCamera::ActiveState) { + m_session->setState(m_state); + } else if (m_reloadPending) { + //handle delayed reload because of busy camera + m_session->setState(QCamera::LoadedState); + QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); + } + } +} + +void CameraBinControl::handleCameraError(int errorCode, const QString &errorString) +{ + emit error(errorCode, errorString); + setState(QCamera::UnloadedState); +} + +void CameraBinControl::delayedReload() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << "CameraBinControl: reload pipeline"; +#endif + if (m_reloadPending) { + m_reloadPending = false; + if (m_state == QCamera::ActiveState && m_session->isReady()) + m_session->setState(QCamera::ActiveState); + } +} + +bool CameraBinControl::canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(status); + + switch (changeType) { + case QCameraControl::Viewfinder: + return true; + case QCameraControl::CaptureMode: + case QCameraControl::ImageEncodingSettings: + case QCameraControl::VideoEncodingSettings: + case QCameraControl::ViewfinderSettings: + default: + return status != QCamera::ActiveStatus; + } +} + +#define VIEWFINDER_COLORSPACE_CONVERSION 0x00000004 + +bool CameraBinControl::viewfinderColorSpaceConversion() const +{ + gint flags = 0; + g_object_get(G_OBJECT(m_session->cameraBin()), "flags", &flags, NULL); + + return flags & VIEWFINDER_COLORSPACE_CONVERSION; +} + +void CameraBinControl::setViewfinderColorSpaceConversion(bool enabled) +{ + gint flags = 0; + g_object_get(G_OBJECT(m_session->cameraBin()), "flags", &flags, NULL); + + if (enabled) + flags |= VIEWFINDER_COLORSPACE_CONVERSION; + else + flags &= ~VIEWFINDER_COLORSPACE_CONVERSION; + + g_object_set(G_OBJECT(m_session->cameraBin()), "flags", flags, NULL); +} + +QCamera::LockTypes CameraBinControl::supportedLocks() const +{ + QCamera::LockTypes locks = QCamera::LockFocus; + + if (GstPhotography *photography = m_session->photography()) { + if (gst_photography_get_capabilities(photography) & GST_PHOTOGRAPHY_CAPS_WB_MODE) + locks |= QCamera::LockWhiteBalance; + + if (GstElement *source = m_session->cameraSource()) { + if (g_object_class_find_property( + G_OBJECT_GET_CLASS(source), "exposure-mode")) { + locks |= QCamera::LockExposure; + } + } + } + + return locks; +} + +QCamera::LockStatus CameraBinControl::lockStatus(QCamera::LockType lock) const +{ + switch (lock) { + case QCamera::LockFocus: + return m_focus->focusStatus(); + case QCamera::LockExposure: + if (m_pendingLocks & QCamera::LockExposure) + return QCamera::Searching; + return isExposureLocked() ? QCamera::Locked : QCamera::Unlocked; + case QCamera::LockWhiteBalance: + if (m_pendingLocks & QCamera::LockWhiteBalance) + return QCamera::Searching; + return isWhiteBalanceLocked() ? QCamera::Locked : QCamera::Unlocked; + default: + return QCamera::Unlocked; + } +} + +void CameraBinControl::searchAndLock(QCamera::LockTypes locks) +{ + m_pendingLocks &= ~locks; + + if (locks & QCamera::LockFocus) { + m_pendingLocks |= QCamera::LockFocus; + m_focus->_q_startFocusing(); + } + if (!m_pendingLocks) + m_lockTimer.stop(); + + if (locks & QCamera::LockExposure) { + if (isExposureLocked()) { + unlockExposure(QCamera::Searching, QCamera::UserRequest); + m_pendingLocks |= QCamera::LockExposure; + m_lockTimer.start(1000, this); + } else { + lockExposure(QCamera::UserRequest); + } + } + if (locks & QCamera::LockWhiteBalance) { + if (isWhiteBalanceLocked()) { + unlockWhiteBalance(QCamera::Searching, QCamera::UserRequest); + m_pendingLocks |= QCamera::LockWhiteBalance; + m_lockTimer.start(1000, this); + } else { + lockWhiteBalance(QCamera::UserRequest); + } + } +} + +void CameraBinControl::unlock(QCamera::LockTypes locks) +{ + m_pendingLocks &= ~locks; + + if (locks & QCamera::LockFocus) + m_focus->_q_stopFocusing(); + + if (!m_pendingLocks) + m_lockTimer.stop(); + + if (locks & QCamera::LockExposure) + unlockExposure(QCamera::Unlocked, QCamera::UserRequest); + if (locks & QCamera::LockWhiteBalance) + unlockWhiteBalance(QCamera::Unlocked, QCamera::UserRequest); +} + +void CameraBinControl::updateFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + if (status != QCamera::Searching) + m_pendingLocks &= ~QCamera::LockFocus; + + if (status == QCamera::Locked && !m_lockTimer.isActive()) { + if (m_pendingLocks & QCamera::LockExposure) + lockExposure(QCamera::LockAcquired); + if (m_pendingLocks & QCamera::LockWhiteBalance) + lockWhiteBalance(QCamera::LockAcquired); + } + emit lockStatusChanged(QCamera::LockFocus, status, reason); +} + +void CameraBinControl::timerEvent(QTimerEvent *event) +{ + if (event->timerId() != m_lockTimer.timerId()) + return QCameraControl::timerEvent(event); + + m_lockTimer.stop(); + + if (!(m_pendingLocks & QCamera::LockFocus)) { + if (m_pendingLocks & QCamera::LockExposure) + lockExposure(QCamera::LockAcquired); + if (m_pendingLocks & QCamera::LockWhiteBalance) + lockWhiteBalance(QCamera::LockAcquired); + } +} + +bool CameraBinControl::isExposureLocked() const +{ + if (GstElement *source = m_session->cameraSource()) { + GstPhotographyExposureMode exposureMode = GST_PHOTOGRAPHY_EXPOSURE_MODE_AUTO; + g_object_get (G_OBJECT(source), "exposure-mode", &exposureMode, NULL); + return exposureMode == GST_PHOTOGRAPHY_EXPOSURE_MODE_MANUAL; + } + + return false; +} + +void CameraBinControl::lockExposure(QCamera::LockChangeReason reason) +{ + GstElement *source = m_session->cameraSource(); + if (!source) + return; + + m_pendingLocks &= ~QCamera::LockExposure; + g_object_set( + G_OBJECT(source), + "exposure-mode", + GST_PHOTOGRAPHY_EXPOSURE_MODE_MANUAL, + NULL); + emit lockStatusChanged(QCamera::LockExposure, QCamera::Locked, reason); +} + +void CameraBinControl::unlockExposure(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + GstElement *source = m_session->cameraSource(); + if (!source) + return; + + g_object_set( + G_OBJECT(source), + "exposure-mode", + GST_PHOTOGRAPHY_EXPOSURE_MODE_AUTO, + NULL); + emit lockStatusChanged(QCamera::LockExposure, status, reason); +} + +bool CameraBinControl::isWhiteBalanceLocked() const +{ + if (GstPhotography *photography = m_session->photography()) { + GstPhotographyWhiteBalanceMode whiteBalanceMode; + return gst_photography_get_white_balance_mode(photography, &whiteBalanceMode) + && whiteBalanceMode == GST_PHOTOGRAPHY_WB_MODE_MANUAL; + } + + return false; +} + +void CameraBinControl::lockWhiteBalance(QCamera::LockChangeReason reason) +{ + m_pendingLocks &= ~QCamera::LockWhiteBalance; + m_session->imageProcessingControl()->lockWhiteBalance(); + emit lockStatusChanged(QCamera::LockWhiteBalance, QCamera::Locked, reason); +} + +void CameraBinControl::unlockWhiteBalance( + QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + m_session->imageProcessingControl()->lockWhiteBalance(); + emit lockStatusChanged(QCamera::LockWhiteBalance, status, reason); +} + +QList<QCameraViewfinderSettings> CameraBinControl::supportedViewfinderSettings() const +{ + return m_session->supportedViewfinderSettings(); +} + +QCameraViewfinderSettings CameraBinControl::viewfinderSettings() const +{ + return m_session->viewfinderSettings(); +} + +void CameraBinControl::setViewfinderSettings(const QCameraViewfinderSettings &settings) +{ + m_session->setViewfinderSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabincontrol_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabincontrol_p.h new file mode 100644 index 000000000..3c1396c82 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabincontrol_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** 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 CAMERABINCONTROL_H +#define CAMERABINCONTROL_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 <QHash> +#include <qbasictimer.h> +#include <qcameracontrol.h> +#include "camerabinsession.h" + +QT_BEGIN_NAMESPACE + +class CameraBinControl : public QCameraControl +{ + Q_OBJECT + Q_PROPERTY(bool viewfinderColorSpaceConversion READ viewfinderColorSpaceConversion WRITE setViewfinderColorSpaceConversion) +public: + CameraBinControl( CameraBinSession *session ); + virtual ~CameraBinControl(); + + bool isValid() const { return true; } + + 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; + bool viewfinderColorSpaceConversion() const; + + 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; + +public slots: + void reloadLater(); + void setViewfinderColorSpaceConversion(bool enabled); + +private slots: + void delayedReload(); + + void handleBusyChanged(bool); + void handleCameraError(int error, const QString &errorString); + +private: + void updateSupportedResolutions(const QString &device); + +protected: + void timerEvent(QTimerEvent *event) override; + +private slots: + void updateFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + +private: + bool isExposureLocked() const; + void lockExposure(QCamera::LockChangeReason reason); + void unlockExposure(QCamera::LockStatus status, QCamera::LockChangeReason reason); + + bool isWhiteBalanceLocked() const; + void lockWhiteBalance(QCamera::LockChangeReason reason); + void unlockWhiteBalance(QCamera::LockStatus status, QCamera::LockChangeReason reason); + + CameraBinSession *m_session; + QCamera::State m_state; + + bool m_reloadPending; + + CameraBinFocus *m_focus; + QBasicTimer m_lockTimer; + QCamera::LockTypes m_pendingLocks; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinexposure.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinexposure.cpp new file mode 100644 index 000000000..740532d22 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinexposure.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** 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 "camerabinexposure.h" +#include "camerabinsession.h" +#include <gst/interfaces/photography.h> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +CameraBinExposure::CameraBinExposure(CameraBinSession *session) + :QCameraExposureControl(session), + m_session(session) +{ +} + +CameraBinExposure::~CameraBinExposure() +{ +} + +bool CameraBinExposure::isParameterSupported(ExposureParameter parameter) const +{ + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + case QCameraExposureControl::ISO: + case QCameraExposureControl::Aperture: + case QCameraExposureControl::ShutterSpeed: + return true; + default: + return false; + } +} + +QVariantList CameraBinExposure::supportedParameterRange(ExposureParameter parameter, + bool *continuous) const +{ + if (continuous) + *continuous = false; + + QVariantList res; + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + if (continuous) + *continuous = true; + res << -2.0 << 2.0; + break; + case QCameraExposureControl::ISO: + res << 100 << 200 << 400; + break; + case QCameraExposureControl::Aperture: + res << 2.8; + break; + default: + break; + } + + return res; +} + +QVariant CameraBinExposure::requestedValue(ExposureParameter parameter) const +{ + return m_requestedValues.value(parameter); +} + +QVariant CameraBinExposure::actualValue(ExposureParameter parameter) const +{ + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + { + gfloat ev; + gst_photography_get_ev_compensation(m_session->photography(), &ev); + return QVariant(ev); + } + case QCameraExposureControl::ISO: + { + guint isoSpeed = 0; + gst_photography_get_iso_speed(m_session->photography(), &isoSpeed); + return QVariant(isoSpeed); + } + case QCameraExposureControl::Aperture: + return QVariant(2.8); + case QCameraExposureControl::ShutterSpeed: + { + guint32 shutterSpeed = 0; + gst_photography_get_exposure(m_session->photography(), &shutterSpeed); + + return QVariant(shutterSpeed/1000000.0); + } + case QCameraExposureControl::ExposureMode: + { + GstPhotographySceneMode sceneMode; + gst_photography_get_scene_mode(m_session->photography(), &sceneMode); + + switch (sceneMode) { + case GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT: + return QVariant::fromValue(QCameraExposure::ExposurePortrait); + case GST_PHOTOGRAPHY_SCENE_MODE_SPORT: + return QVariant::fromValue(QCameraExposure::ExposureSports); + case GST_PHOTOGRAPHY_SCENE_MODE_NIGHT: + return QVariant::fromValue(QCameraExposure::ExposureNight); + case GST_PHOTOGRAPHY_SCENE_MODE_MANUAL: + return QVariant::fromValue(QCameraExposure::ExposureManual); + case GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE: + return QVariant::fromValue(QCameraExposure::ExposureLandscape); + case GST_PHOTOGRAPHY_SCENE_MODE_SNOW: + return QVariant::fromValue(QCameraExposure::ExposureSnow); + case GST_PHOTOGRAPHY_SCENE_MODE_BEACH: + return QVariant::fromValue(QCameraExposure::ExposureBeach); + case GST_PHOTOGRAPHY_SCENE_MODE_ACTION: + return QVariant::fromValue(QCameraExposure::ExposureAction); + case GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT: + return QVariant::fromValue(QCameraExposure::ExposureNightPortrait); + case GST_PHOTOGRAPHY_SCENE_MODE_THEATRE: + return QVariant::fromValue(QCameraExposure::ExposureTheatre); + case GST_PHOTOGRAPHY_SCENE_MODE_SUNSET: + return QVariant::fromValue(QCameraExposure::ExposureSunset); + case GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO: + return QVariant::fromValue(QCameraExposure::ExposureSteadyPhoto); + case GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS: + return QVariant::fromValue(QCameraExposure::ExposureFireworks); + case GST_PHOTOGRAPHY_SCENE_MODE_PARTY: + return QVariant::fromValue(QCameraExposure::ExposureParty); + case GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT: + return QVariant::fromValue(QCameraExposure::ExposureCandlelight); + case GST_PHOTOGRAPHY_SCENE_MODE_BARCODE: + return QVariant::fromValue(QCameraExposure::ExposureBarcode); + //no direct mapping available so mapping to auto mode + case GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP: + case GST_PHOTOGRAPHY_SCENE_MODE_AUTO: + default: + return QVariant::fromValue(QCameraExposure::ExposureAuto); + } + } + default: + return QVariant(); + } +} + +bool CameraBinExposure::setValue(ExposureParameter parameter, const QVariant& value) +{ + QVariant oldValue = actualValue(parameter); + + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + gst_photography_set_ev_compensation(m_session->photography(), value.toReal()); + break; + case QCameraExposureControl::ISO: + gst_photography_set_iso_speed(m_session->photography(), value.toInt()); + break; + case QCameraExposureControl::Aperture: + gst_photography_set_aperture(m_session->photography(), guint(value.toReal()*1000000)); + break; + case QCameraExposureControl::ShutterSpeed: + gst_photography_set_exposure(m_session->photography(), guint(value.toReal()*1000000)); + break; + case QCameraExposureControl::ExposureMode: + { + QCameraExposure::ExposureMode mode = value.value<QCameraExposure::ExposureMode>(); + GstPhotographySceneMode sceneMode; + + gst_photography_get_scene_mode(m_session->photography(), &sceneMode); + + switch (mode) { + case QCameraExposure::ExposureManual: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_MANUAL; + break; + case QCameraExposure::ExposurePortrait: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT; + break; + case QCameraExposure::ExposureSports: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SPORT; + break; + case QCameraExposure::ExposureNight: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT; + break; + case QCameraExposure::ExposureAuto: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO; + break; + case QCameraExposure::ExposureLandscape: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE; + break; + case QCameraExposure::ExposureSnow: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SNOW; + break; + case QCameraExposure::ExposureBeach: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BEACH; + break; + case QCameraExposure::ExposureAction: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_ACTION; + break; + case QCameraExposure::ExposureNightPortrait: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT_PORTRAIT; + break; + case QCameraExposure::ExposureTheatre: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_THEATRE; + break; + case QCameraExposure::ExposureSunset: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SUNSET; + break; + case QCameraExposure::ExposureSteadyPhoto: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_STEADY_PHOTO; + break; + case QCameraExposure::ExposureFireworks: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_FIREWORKS; + break; + case QCameraExposure::ExposureParty: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PARTY; + break; + case QCameraExposure::ExposureCandlelight: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_CANDLELIGHT; + break; + case QCameraExposure::ExposureBarcode: + sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_BARCODE; + break; + default: + break; + } + + gst_photography_set_scene_mode(m_session->photography(), sceneMode); + break; + } + default: + return false; + } + + if (!qFuzzyCompare(m_requestedValues.value(parameter).toReal(), value.toReal())) { + m_requestedValues[parameter] = value; + emit requestedValueChanged(parameter); + } + + QVariant newValue = actualValue(parameter); + if (!qFuzzyCompare(oldValue.toReal(), newValue.toReal())) + emit actualValueChanged(parameter); + + return true; +} + +QCameraExposure::FlashModes CameraBinExposure::flashMode() const +{ + GstPhotographyFlashMode flashMode; + gst_photography_get_flash_mode(m_session->photography(), &flashMode); + + QCameraExposure::FlashModes modes; + switch (flashMode) { + case GST_PHOTOGRAPHY_FLASH_MODE_AUTO: modes |= QCameraExposure::FlashAuto; break; + case GST_PHOTOGRAPHY_FLASH_MODE_OFF: modes |= QCameraExposure::FlashOff; break; + case GST_PHOTOGRAPHY_FLASH_MODE_ON: modes |= QCameraExposure::FlashOn; break; + case GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN: modes |= QCameraExposure::FlashFill; break; + case GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE: modes |= QCameraExposure::FlashRedEyeReduction; break; + default: + modes |= QCameraExposure::FlashAuto; + break; + } + return modes; +} + +void CameraBinExposure::setFlashMode(QCameraExposure::FlashModes mode) +{ + GstPhotographyFlashMode flashMode; + gst_photography_get_flash_mode(m_session->photography(), &flashMode); + + if (mode.testFlag(QCameraExposure::FlashAuto)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_AUTO; + else if (mode.testFlag(QCameraExposure::FlashOff)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_OFF; + else if (mode.testFlag(QCameraExposure::FlashOn)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_ON; + else if (mode.testFlag(QCameraExposure::FlashFill)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN; + else if (mode.testFlag(QCameraExposure::FlashRedEyeReduction)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE; + + gst_photography_set_flash_mode(m_session->photography(), flashMode); +} + +bool CameraBinExposure::isFlashModeSupported(QCameraExposure::FlashModes mode) const +{ + return mode == QCameraExposure::FlashOff || + mode == QCameraExposure::FlashOn || + mode == QCameraExposure::FlashAuto || + mode == QCameraExposure::FlashRedEyeReduction || + mode == QCameraExposure::FlashFill; +} + +bool CameraBinExposure::isFlashReady() const +{ + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinexposure_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinexposure_p.h new file mode 100644 index 000000000..113187bc8 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinexposure_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 CAMERABINEXPOSURECONTROL_H +#define CAMERABINEXPOSURECONTROL_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 <qcameraexposurecontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +QT_BEGIN_NAMESPACE + +class CameraBinSession; + +class CameraBinExposure : public QCameraExposureControl +{ + Q_OBJECT + +public: + CameraBinExposure(CameraBinSession *session); + virtual ~CameraBinExposure(); + + 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: + CameraBinSession *m_session; + QHash<ExposureParameter, QVariant> m_requestedValues; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINEXPOSURECONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinfocus.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinfocus.cpp new file mode 100644 index 000000000..f795b0f2f --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinfocus.cpp @@ -0,0 +1,607 @@ +/**************************************************************************** +** +** 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 "camerabinfocus.h" +#include "camerabinsession.h" + +#include <gst/interfaces/photography.h> + +#include <QDebug> +#include <QtCore/qcoreevent.h> +#include <QtCore/qmetaobject.h> + +#include <private/qgstutils_p.h> + +#define ZOOM_PROPERTY "zoom" +#define MAX_ZOOM_PROPERTY "max-zoom" + +//#define CAMERABIN_DEBUG 1 + +QT_BEGIN_NAMESPACE + +CameraBinFocus::CameraBinFocus(CameraBinSession *session) + :QCameraFocusControl(session), + QGstreamerBufferProbe(ProbeBuffers), + m_session(session), + m_cameraStatus(QCamera::UnloadedStatus), + m_focusMode(QCameraFocus::AutoFocus), + m_focusPointMode(QCameraFocus::FocusPointAuto), + m_focusStatus(QCamera::Unlocked), + m_focusZoneStatus(QCameraFocusZone::Selected), + m_focusPoint(0.5, 0.5), + m_focusRect(0, 0, 0.3, 0.3) +{ + m_focusRect.moveCenter(m_focusPoint); + + gst_photography_set_focus_mode(m_session->photography(), GST_PHOTOGRAPHY_FOCUS_MODE_AUTO); + + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), + this, SLOT(_q_handleCameraStatusChange(QCamera::Status))); + + GstElement *camerabin = m_session->cameraBin(); + g_signal_connect(G_OBJECT(camerabin), "notify::zoom", G_CALLBACK(updateZoom), this); + g_signal_connect(G_OBJECT(camerabin), "notify::max-zoom", G_CALLBACK(updateMaxZoom), this); +} + +CameraBinFocus::~CameraBinFocus() +{ +} + +QCameraFocus::FocusModes CameraBinFocus::focusMode() const +{ + return m_focusMode; +} + +void CameraBinFocus::setFocusMode(QCameraFocus::FocusModes mode) +{ + GstPhotographyFocusMode photographyMode; + + switch (mode) { + case QCameraFocus::AutoFocus: + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_AUTO; + break; + case QCameraFocus::HyperfocalFocus: + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_HYPERFOCAL; + break; + case QCameraFocus::InfinityFocus: + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_INFINITY; + break; + case QCameraFocus::ContinuousFocus: + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_CONTINUOUS_NORMAL; + break; + case QCameraFocus::MacroFocus: + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_MACRO; + break; + default: + if (mode & QCameraFocus::AutoFocus) { + photographyMode = GST_PHOTOGRAPHY_FOCUS_MODE_AUTO; + break; + } else { + return; + } + } + + if (gst_photography_set_focus_mode(m_session->photography(), photographyMode)) + m_focusMode = mode; +} + +bool CameraBinFocus::isFocusModeSupported(QCameraFocus::FocusModes mode) const +{ + switch (mode) { + case QCameraFocus::AutoFocus: + case QCameraFocus::HyperfocalFocus: + case QCameraFocus::InfinityFocus: + case QCameraFocus::ContinuousFocus: + case QCameraFocus::MacroFocus: + return true; + default: + return mode & QCameraFocus::AutoFocus; + } +} + +QCameraFocus::FocusPointMode CameraBinFocus::focusPointMode() const +{ + return m_focusPointMode; +} + +void CameraBinFocus::setFocusPointMode(QCameraFocus::FocusPointMode mode) +{ + GstElement *source = m_session->cameraSource(); + + if (m_focusPointMode == mode || !source) + return; + + if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection) { + g_object_set (G_OBJECT(source), "detect-faces", FALSE, NULL); + + if (GstPad *pad = gst_element_get_static_pad(source, "vfsrc")) { + removeProbeFromPad(pad); + gst_object_unref(GST_OBJECT(pad)); + } + + m_faceResetTimer.stop(); + m_faceFocusRects.clear(); + + QMutexLocker locker(&m_mutex); + m_faces.clear(); + } + + if (m_focusPointMode != QCameraFocus::FocusPointAuto) + resetFocusPoint(); + + switch (mode) { + case QCameraFocus::FocusPointAuto: + case QCameraFocus::FocusPointCustom: + break; + case QCameraFocus::FocusPointFaceDetection: + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "detect-faces")) { + if (GstPad *pad = gst_element_get_static_pad(source, "vfsrc")) { + addProbeToPad(pad); + g_object_set (G_OBJECT(source), "detect-faces", TRUE, NULL); + break; + } + } + return; + default: + return; + } + + m_focusPointMode = mode; + emit focusPointModeChanged(m_focusPointMode); + emit focusZonesChanged(); +} + +bool CameraBinFocus::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const +{ + switch (mode) { + case QCameraFocus::FocusPointAuto: + case QCameraFocus::FocusPointCustom: + return true; + case QCameraFocus::FocusPointFaceDetection: + if (GstElement *source = m_session->cameraSource()) + return g_object_class_find_property(G_OBJECT_GET_CLASS(source), "detect-faces"); + return false; + default: + return false; + } +} + +QPointF CameraBinFocus::customFocusPoint() const +{ + return m_focusPoint; +} + +void CameraBinFocus::setCustomFocusPoint(const QPointF &point) +{ + if (m_focusPoint != point) { + m_focusPoint = point; + + // Bound the focus point so the focus rect remains entirely within the unit square. + m_focusPoint.setX(qBound(m_focusRect.width() / 2, m_focusPoint.x(), 1 - m_focusRect.width() / 2)); + m_focusPoint.setY(qBound(m_focusRect.height() / 2, m_focusPoint.y(), 1 - m_focusRect.height() / 2)); + + if (m_focusPointMode == QCameraFocus::FocusPointCustom) { + const QRectF focusRect = m_focusRect; + m_focusRect.moveCenter(m_focusPoint); + + updateRegionOfInterest(m_focusRect); + + if (focusRect != m_focusRect) { + emit focusZonesChanged(); + } + } + + emit customFocusPointChanged(m_focusPoint); + } +} + +QCameraFocusZoneList CameraBinFocus::focusZones() const +{ + QCameraFocusZoneList zones; + + if (m_focusPointMode != QCameraFocus::FocusPointFaceDetection) { + zones.append(QCameraFocusZone(m_focusRect, m_focusZoneStatus)); + } else for (const QRect &face : qAsConst(m_faceFocusRects)) { + const QRectF normalizedRect( + face.x() / qreal(m_viewfinderResolution.width()), + face.y() / qreal(m_viewfinderResolution.height()), + face.width() / qreal(m_viewfinderResolution.width()), + face.height() / qreal(m_viewfinderResolution.height())); + zones.append(QCameraFocusZone(normalizedRect, m_focusZoneStatus)); + } + return zones; +} + +void CameraBinFocus::handleFocusMessage(GstMessage *gm) +{ + //it's a sync message, so it's called from non main thread + const GstStructure *structure = gst_message_get_structure(gm); + if (gst_structure_has_name(structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) { + gint status = GST_PHOTOGRAPHY_FOCUS_STATUS_NONE; + gst_structure_get_int (structure, "status", &status); + QCamera::LockStatus focusStatus = m_focusStatus; + QCamera::LockChangeReason reason = QCamera::UserRequest; + + switch (status) { + case GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL: + focusStatus = QCamera::Unlocked; + reason = QCamera::LockFailed; + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS: + focusStatus = QCamera::Locked; + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_NONE: + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING: + focusStatus = QCamera::Searching; + break; + default: + break; + } + + static int signalIndex = metaObject()->indexOfSlot( + "_q_setFocusStatus(QCamera::LockStatus,QCamera::LockChangeReason)"); + metaObject()->method(signalIndex).invoke(this, + Qt::QueuedConnection, + Q_ARG(QCamera::LockStatus,focusStatus), + Q_ARG(QCamera::LockChangeReason,reason)); + } +} + +void CameraBinFocus::_q_setFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ +#ifdef CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Current:" + << m_focusStatus + << "New:" + << status << reason; +#endif + + if (m_focusStatus != status) { + m_focusStatus = status; + + QCameraFocusZone::FocusZoneStatus zonesStatus = + m_focusStatus == QCamera::Locked ? + QCameraFocusZone::Focused : QCameraFocusZone::Selected; + + if (m_focusZoneStatus != zonesStatus) { + m_focusZoneStatus = zonesStatus; + emit focusZonesChanged(); + } + + if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection + && m_focusStatus == QCamera::Unlocked) { + _q_updateFaces(); + } + + emit _q_focusStatusChanged(m_focusStatus, reason); + } +} + +void CameraBinFocus::_q_handleCameraStatusChange(QCamera::Status status) +{ + m_cameraStatus = status; + if (status == QCamera::ActiveStatus) { + if (GstPad *pad = gst_element_get_static_pad(m_session->cameraSource(), "vfsrc")) { + if (GstCaps *caps = qt_gst_pad_get_current_caps(pad)) { + if (GstStructure *structure = gst_caps_get_structure(caps, 0)) { + int width = 0; + int height = 0; + gst_structure_get_int(structure, "width", &width); + gst_structure_get_int(structure, "height", &height); + setViewfinderResolution(QSize(width, height)); + } + gst_caps_unref(caps); + } + gst_object_unref(GST_OBJECT(pad)); + } + if (m_focusPointMode == QCameraFocus::FocusPointCustom) { + updateRegionOfInterest(m_focusRect); + } + } else { + _q_setFocusStatus(QCamera::Unlocked, QCamera::LockLost); + + resetFocusPoint(); + } +} + +void CameraBinFocus::_q_startFocusing() +{ + _q_setFocusStatus(QCamera::Searching, QCamera::UserRequest); + gst_photography_set_autofocus(m_session->photography(), TRUE); +} + +void CameraBinFocus::_q_stopFocusing() +{ + gst_photography_set_autofocus(m_session->photography(), FALSE); + _q_setFocusStatus(QCamera::Unlocked, QCamera::UserRequest); +} + +void CameraBinFocus::setViewfinderResolution(const QSize &resolution) +{ + if (resolution != m_viewfinderResolution) { + m_viewfinderResolution = resolution; + if (!resolution.isEmpty()) { + const QPointF center = m_focusRect.center(); + m_focusRect.setWidth(m_focusRect.height() * resolution.height() / resolution.width()); + m_focusRect.moveCenter(center); + } + } +} + +void CameraBinFocus::resetFocusPoint() +{ + const QRectF focusRect = m_focusRect; + m_focusPoint = QPointF(0.5, 0.5); + m_focusRect.moveCenter(m_focusPoint); + + updateRegionOfInterest(QList<QRect>()); + + if (focusRect != m_focusRect) { + emit customFocusPointChanged(m_focusPoint); + emit focusZonesChanged(); + } +} + +static void appendRegion(GValue *regions, int priority, const QRect &rectangle) +{ + GstStructure *region = gst_structure_new( + "region", + "region-x" , G_TYPE_UINT , rectangle.x(), + "region-y" , G_TYPE_UINT, rectangle.y(), + "region-w" , G_TYPE_UINT , rectangle.width(), + "region-h" , G_TYPE_UINT, rectangle.height(), + "region-priority" , G_TYPE_UINT, priority, + NULL); + + GValue regionValue = G_VALUE_INIT; + g_value_init(®ionValue, GST_TYPE_STRUCTURE); + gst_value_set_structure(®ionValue, region); + gst_structure_free(region); + + gst_value_list_append_value(regions, ®ionValue); + g_value_unset(®ionValue); +} + +void CameraBinFocus::updateRegionOfInterest(const QRectF &rectangle) +{ + updateRegionOfInterest(QList<QRect>() << QRect( + rectangle.x() * m_viewfinderResolution.width(), + rectangle.y() * m_viewfinderResolution.height(), + rectangle.width() * m_viewfinderResolution.width(), + rectangle.height() * m_viewfinderResolution.height())); +} + +void CameraBinFocus::updateRegionOfInterest(const QList<QRect> &rectangles) +{ + if (m_cameraStatus != QCamera::ActiveStatus) + return; + + GstElement * const cameraSource = m_session->cameraSource(); + if (!cameraSource) + return; + + GValue regions = G_VALUE_INIT; + g_value_init(®ions, GST_TYPE_LIST); + + if (rectangles.isEmpty()) { + appendRegion(®ions, 0, QRect(0, 0, 0, 0)); + } else { + // Add padding around small face rectangles so the auto focus has a reasonable amount + // of image to work with. + const int minimumDimension = qMin( + m_viewfinderResolution.width(), m_viewfinderResolution.height()) * 0.3; + const QRect viewfinderRectangle(QPoint(0, 0), m_viewfinderResolution); + + for (const QRect &rectangle : rectangles) { + QRect paddedRectangle( + 0, + 0, + qMax(rectangle.width(), minimumDimension), + qMax(rectangle.height(), minimumDimension)); + paddedRectangle.moveCenter(rectangle.center()); + + appendRegion(®ions, 1, viewfinderRectangle.intersected(paddedRectangle)); + } + } + + GstStructure *regionsOfInterest = gst_structure_new( + "regions-of-interest", + "frame-width" , G_TYPE_UINT , m_viewfinderResolution.width(), + "frame-height" , G_TYPE_UINT, m_viewfinderResolution.height(), + NULL); + gst_structure_set_value(regionsOfInterest, "regions", ®ions); + g_value_unset(®ions); + + GstEvent *event = gst_event_new_custom(GST_EVENT_CUSTOM_UPSTREAM, regionsOfInterest); + gst_element_send_event(cameraSource, event); +} + +void CameraBinFocus::_q_updateFaces() +{ + if (m_focusPointMode != QCameraFocus::FocusPointFaceDetection + || m_focusStatus != QCamera::Unlocked) { + return; + } + + QList<QRect> faces; + + { + QMutexLocker locker(&m_mutex); + faces = m_faces; + } + + if (!faces.isEmpty()) { + m_faceResetTimer.stop(); + m_faceFocusRects = faces; + updateRegionOfInterest(m_faceFocusRects); + emit focusZonesChanged(); + } else { + m_faceResetTimer.start(500, this); + } +} + +void CameraBinFocus::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == m_faceResetTimer.timerId()) { + m_faceResetTimer.stop(); + + if (m_focusStatus == QCamera::Unlocked) { + m_faceFocusRects.clear(); + updateRegionOfInterest(m_faceFocusRects); + emit focusZonesChanged(); + } + } else { + QCameraFocusControl::timerEvent(event); + } +} + +bool CameraBinFocus::probeBuffer(GstBuffer *buffer) +{ + QList<QRect> faces; + + gpointer state = NULL; + const GstMetaInfo *info = GST_VIDEO_REGION_OF_INTEREST_META_INFO; + + while (GstMeta *meta = gst_buffer_iterate_meta(buffer, &state)) { + if (meta->info->api != info->api) + continue; + + GstVideoRegionOfInterestMeta *region = reinterpret_cast<GstVideoRegionOfInterestMeta *>(meta); + + faces.append(QRect(region->x, region->y, region->w, region->h)); + } + + QMutexLocker locker(&m_mutex); + + if (m_faces != faces) { + m_faces = faces; + + static const int signalIndex = metaObject()->indexOfSlot("_q_updateFaces()"); + metaObject()->method(signalIndex).invoke(this, Qt::QueuedConnection); + } + + return true; +} + +qreal CameraBinFocus::maximumOpticalZoom() const +{ + return 1.0; +} + +qreal CameraBinFocus::maximumDigitalZoom() const +{ + gfloat zoomFactor = 1.0; + g_object_get(GST_BIN(m_session->cameraBin()), MAX_ZOOM_PROPERTY, &zoomFactor, NULL); + return zoomFactor; +} + +qreal CameraBinFocus::requestedDigitalZoom() const +{ + return m_requestedDigitalZoom; +} + +qreal CameraBinFocus::requestedOpticalZoom() const +{ + return m_requestedOpticalZoom; +} + +qreal CameraBinFocus::currentOpticalZoom() const +{ + return 1.0; +} + +qreal CameraBinFocus::currentDigitalZoom() const +{ + gfloat zoomFactor = 1.0; + g_object_get(GST_BIN(m_session->cameraBin()), ZOOM_PROPERTY, &zoomFactor, NULL); + return zoomFactor; +} + +void CameraBinFocus::zoomTo(qreal optical, qreal digital) +{ + qreal oldDigitalZoom = currentDigitalZoom(); + + if (m_requestedDigitalZoom != digital) { + m_requestedDigitalZoom = digital; + emit requestedDigitalZoomChanged(digital); + } + + if (m_requestedOpticalZoom != optical) { + m_requestedOpticalZoom = optical; + emit requestedOpticalZoomChanged(optical); + } + + digital = qBound(qreal(1.0), digital, maximumDigitalZoom()); + g_object_set(GST_BIN(m_session->cameraBin()), ZOOM_PROPERTY, digital, NULL); + + qreal newDigitalZoom = currentDigitalZoom(); + if (!qFuzzyCompare(oldDigitalZoom, newDigitalZoom)) + emit currentDigitalZoomChanged(digital); +} + +void CameraBinFocus::updateZoom(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + + gfloat zoomFactor = 1.0; + g_object_get(o, ZOOM_PROPERTY, &zoomFactor, NULL); + + CameraBinFocus *zoom = reinterpret_cast<CameraBinFocus *>(d); + + QMetaObject::invokeMethod(zoom, "currentDigitalZoomChanged", + Qt::QueuedConnection, + Q_ARG(qreal, zoomFactor)); +} + +void CameraBinFocus::updateMaxZoom(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + + gfloat zoomFactor = 1.0; + g_object_get(o, MAX_ZOOM_PROPERTY, &zoomFactor, NULL); + + CameraBinFocus *zoom = reinterpret_cast<CameraBinFocus *>(d); + + QMetaObject::invokeMethod(zoom, "maximumDigitalZoomChanged", + Qt::QueuedConnection, + Q_ARG(qreal, zoomFactor)); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinfocus_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinfocus_p.h new file mode 100644 index 000000000..4ffef7a3a --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinfocus_p.h @@ -0,0 +1,153 @@ +/**************************************************************************** +** +** 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 CAMERABINFOCUSCONTROL_H +#define CAMERABINFOCUSCONTROL_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 <qcamerafocuscontrol.h> + +#include <private/qgstreamerbufferprobe_p.h> + +#include <qbasictimer.h> +#include <qlist.h> +#include <qmutex.h> + +#include <gst/gst.h> +#include <glib.h> + +QT_BEGIN_NAMESPACE + +class CameraBinSession; + +class CameraBinFocus + : public QCameraFocusControl + , QGstreamerBufferProbe +{ + Q_OBJECT + +public: + CameraBinFocus(CameraBinSession *session); + virtual ~CameraBinFocus(); + + 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) 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; + + void handleFocusMessage(GstMessage*); + QCamera::LockStatus focusStatus() const { return m_focusStatus; } + +Q_SIGNALS: + void _q_focusStatusChanged(QCamera::LockStatus status, QCamera::LockChangeReason reason); + +public Q_SLOTS: + void _q_startFocusing(); + void _q_stopFocusing(); + + void setViewfinderResolution(const QSize &resolution); + +protected: + void timerEvent(QTimerEvent *event) override; + +private Q_SLOTS: + void _q_setFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + void _q_handleCameraStatusChange(QCamera::Status status); + void _q_updateFaces(); + +private: + void resetFocusPoint(); + void updateRegionOfInterest(const QRectF &rectangle); + void updateRegionOfInterest(const QList<QRect> &rectangles); + bool probeBuffer(GstBuffer *buffer) override; + + CameraBinSession *m_session; + QCamera::Status m_cameraStatus; + QCameraFocus::FocusModes m_focusMode; + QCameraFocus::FocusPointMode m_focusPointMode; + QCamera::LockStatus m_focusStatus; + QCameraFocusZone::FocusZoneStatus m_focusZoneStatus; + QPointF m_focusPoint; + QRectF m_focusRect; + QSize m_viewfinderResolution; + QList<QRect> m_faces; + QList<QRect> m_faceFocusRects; + QBasicTimer m_faceResetTimer; + mutable QMutex m_mutex; + + static void updateZoom(GObject *o, GParamSpec *p, gpointer d); + static void updateMaxZoom(GObject *o, GParamSpec *p, gpointer d); + + qreal m_requestedOpticalZoom = 1.; + qreal m_requestedDigitalZoom = 1.; + +}; + +QT_END_NAMESPACE + +#endif // CAMERABINFOCUSCONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture.cpp new file mode 100644 index 000000000..36fb4c205 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture.cpp @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** 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 "camerabinimagecapture.h" +#include "camerabincontrol.h" +#include "camerabinsession.h" +#include <private/qgstvideobuffer_p.h> +#include <private/qgstutils_p.h> +#include <QtMultimedia/qmediametadata.h> +#include <QtCore/qdebug.h> +#include <QtCore/qbuffer.h> +#include <QtGui/qimagereader.h> + +//#define DEBUG_CAPTURE + +#define IMAGE_DONE_SIGNAL "image-done" + +QT_BEGIN_NAMESPACE + +CameraBinImageCapture::CameraBinImageCapture(CameraBinSession *session) + :QCameraImageCaptureControl(session) + , m_encoderProbe(this) + , m_muxerProbe(this) + , m_session(session) + , m_jpegEncoderElement(0) + , m_metadataMuxerElement(0) + , m_requestId(0) + , m_ready(false) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateState())); + connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int))); + connect(m_session, SIGNAL(imageCaptured(int,QImage)), this, SIGNAL(imageCaptured(int,QImage))); + + m_session->bus()->installMessageFilter(this); +} + +CameraBinImageCapture::~CameraBinImageCapture() +{ +} + +bool CameraBinImageCapture::isReadyForCapture() const +{ + return m_ready; +} + +int CameraBinImageCapture::capture(const QString &fileName) +{ + m_requestId++; + + if (!m_ready) { + emit error(m_requestId, QCameraImageCapture::NotReadyError, tr("Camera not ready")); + return m_requestId; + } + +#ifdef DEBUG_CAPTURE + qDebug() << Q_FUNC_INFO << m_requestId << fileName; +#endif + m_session->captureImage(m_requestId, fileName); + return m_requestId; +} + +void CameraBinImageCapture::cancelCapture() +{ +} + +QCameraImageCapture::CaptureDestinations CameraBinImageCapture::captureDestination() const +{ + return m_destination; +} + +void CameraBinImageCapture::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + m_destination = destination; +} + + +void CameraBinImageCapture::updateState() +{ + bool ready = m_session->status() == QCamera::ActiveStatus; + if (m_ready != ready) { +#ifdef DEBUG_CAPTURE + qDebug() << "readyForCaptureChanged" << ready; +#endif + emit readyForCaptureChanged(m_ready = ready); + } +} + +GstPadProbeReturn CameraBinImageCapture::encoderEventProbe( + GstPad *, GstPadProbeInfo *info, gpointer user_data) +{ + GstEvent * const event = gst_pad_probe_info_get_event(info); + CameraBinImageCapture * const self = static_cast<CameraBinImageCapture *>(user_data); + if (event && GST_EVENT_TYPE(event) == GST_EVENT_TAG) { + GstTagList *gstTags; + gst_event_parse_tag(event, &gstTags); + QMap<QByteArray, QVariant> extendedTags = QGstUtils::gstTagListToMap(gstTags); + +#ifdef DEBUG_CAPTURE + qDebug() << QString(gst_structure_to_string(gst_event_get_structure(event))).right(768); + qDebug() << "Capture event probe" << extendedTags; +#endif + + QVariantMap tags; + tags[QMediaMetaData::ISOSpeedRatings] = extendedTags.value("capturing-iso-speed"); + tags[QMediaMetaData::DigitalZoomRatio] = extendedTags.value("capturing-digital-zoom-ratio"); + tags[QMediaMetaData::ExposureTime] = extendedTags.value("capturing-shutter-speed"); + tags[QMediaMetaData::WhiteBalance] = extendedTags.value("capturing-white-balance"); + tags[QMediaMetaData::Flash] = extendedTags.value("capturing-flash-fired"); + tags[QMediaMetaData::FocalLengthIn35mmFilm] = extendedTags.value("capturing-focal-length"); + tags[QMediaMetaData::ExposureMode] = extendedTags.value("capturing-exposure-mode"); + tags[QMediaMetaData::FNumber] = extendedTags.value("capturing-focal-ratio"); + tags[QMediaMetaData::ExposureMode] = extendedTags.value("capturing-exposure-mode"); + + for (auto i = tags.cbegin(), end = tags.cend(); i != end; ++i) { + if (i.value().isValid()) { + QMetaObject::invokeMethod(self, "imageMetadataAvailable", + Qt::QueuedConnection, + Q_ARG(int, self->m_requestId), + Q_ARG(QString, i.key()), + Q_ARG(QVariant, i.value())); + } + } + } + return GST_PAD_PROBE_OK; +} + +void CameraBinImageCapture::EncoderProbe::probeCaps(GstCaps *caps) +{ + capture->m_bufferFormat = QGstUtils::formatForCaps(caps, &capture->m_videoInfo); +} + +bool CameraBinImageCapture::EncoderProbe::probeBuffer(GstBuffer *) +{ +#ifdef DEBUG_CAPTURE + qDebug() << "Uncompressed buffer probe"; +#endif + // keep the buffer so we can capture to file or encode the image + return true; +} + +void CameraBinImageCapture::MuxerProbe::probeCaps(GstCaps *caps) +{ + capture->m_jpegResolution = QGstUtils::capsCorrectedResolution(caps); +} + +bool CameraBinImageCapture::MuxerProbe::probeBuffer(GstBuffer *buffer) +{ + QCameraImageCapture::CaptureDestinations destination = capture->m_destination; + + if (destination & QCameraImageCapture::CaptureToBuffer) { + QSize resolution = capture->m_jpegResolution; + //if resolution is not presented in caps, try to find it from encoded jpeg data: + GstMapInfo mapInfo; + if (resolution.isEmpty() && gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) { + QBuffer data; + data.setData(reinterpret_cast<const char*>(mapInfo.data), mapInfo.size); + + QImageReader reader(&data, "JPEG"); + resolution = reader.size(); + + gst_buffer_unmap(buffer, &mapInfo); + } + + GstVideoInfo info; + gst_video_info_set_format( + &info, GST_VIDEO_FORMAT_ENCODED, resolution.width(), resolution.height()); + QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, info); + + QVideoFrame frame(videoBuffer, + resolution, + QVideoFrame::Format_Jpeg); + QMetaObject::invokeMethod(capture, "imageAvailable", + Qt::QueuedConnection, + Q_ARG(int, capture->m_requestId), + Q_ARG(QVideoFrame, frame)); + } + + + // Theoretically we could drop the buffer here when don't want to capture to file but that + // prevents camerabin from recognizing that capture has been completed and returning + // to its idle state. + return true; +} + + +bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message) +{ + //Install metadata event and buffer probes + + //The image capture pipiline is built dynamically, + //it's necessary to wait until jpeg encoder is added to pipeline + + GstMessage *gm = message.rawMessage(); + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) { + GstState oldState; + GstState newState; + GstState pending; + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + if (newState == GST_STATE_READY) { + GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(gm)); + if (!element) + return false; + + gchar *name = gst_element_get_name(element); + QString elementName = QString::fromLatin1(name); + g_free(name); + if (elementName.contains("jpegenc") && element != m_jpegEncoderElement) { + m_jpegEncoderElement = element; + GstPad *sinkpad = gst_element_get_static_pad(element, "sink"); + + //metadata event probe is installed before jpeg encoder + //to emit metadata available signal as soon as possible. +#ifdef DEBUG_CAPTURE + qDebug() << "install metadata probe"; +#endif + gst_pad_add_probe( + sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, encoderEventProbe, this, NULL); +#ifdef DEBUG_CAPTURE + qDebug() << "install uncompressed buffer probe"; +#endif + m_encoderProbe.addProbeToPad(sinkpad, true); + + gst_object_unref(sinkpad); + } else if ((elementName.contains("jifmux") || elementName.startsWith("metadatamux")) + && element != m_metadataMuxerElement) { + //Jpeg encoded buffer probe is added after jifmux/metadatamux + //element to ensure the resulting jpeg buffer contains capture metadata + m_metadataMuxerElement = element; + + GstPad *srcpad = gst_element_get_static_pad(element, "src"); +#ifdef DEBUG_CAPTURE + qDebug() << "install jpeg buffer probe"; +#endif + m_muxerProbe.addProbeToPad(srcpad); + + gst_object_unref(srcpad); + } + } + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) { + if (GST_MESSAGE_SRC(gm) == (GstObject *)m_session->cameraBin()) { + const GstStructure *structure = gst_message_get_structure(gm); + + if (gst_structure_has_name (structure, "image-done")) { + const gchar *fileName = gst_structure_get_string (structure, "filename"); +#ifdef DEBUG_CAPTURE + qDebug() << "Image saved" << fileName; +#endif + + if (m_destination & QCameraImageCapture::CaptureToFile) { + emit imageSaved(m_requestId, QString::fromUtf8(fileName)); + } else { +#ifdef DEBUG_CAPTURE + qDebug() << Q_FUNC_INFO << "Dropped saving file" << fileName; +#endif + QFileInfo info(QString::fromUtf8(fileName)); + if (info.exists() && info.isFile()) + QFile(info.absoluteFilePath()).remove(); + } + } + } + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture_p.h new file mode 100644 index 000000000..b91987bcd --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimagecapture_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 CAMERABINIMAGECAPTURECONTROL_H +#define CAMERABINIMAGECAPTURECONTROL_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.h> +#include "camerabinsession.h" + +#include <qvideosurfaceformat.h> + +#include <private/qgstreamerbufferprobe_p.h> + +#include <gst/video/video.h> + +QT_BEGIN_NAMESPACE + +class CameraBinImageCapture : public QCameraImageCaptureControl, public QGstreamerBusMessageFilter +{ + Q_OBJECT + Q_INTERFACES(QGstreamerBusMessageFilter) +public: + CameraBinImageCapture(CameraBinSession *session); + virtual ~CameraBinImageCapture(); + + QCameraImageCapture::DriveMode driveMode() const override { return QCameraImageCapture::SingleImageCapture; } + void setDriveMode(QCameraImageCapture::DriveMode) override {} + + bool isReadyForCapture() const override; + int capture(const QString &fileName) override; + void cancelCapture() override; + + QCameraImageCapture::CaptureDestinations captureDestination() const override; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) override; + + bool processBusMessage(const QGstreamerMessage &message) override; + +private slots: + void updateState(); + +private: + static GstPadProbeReturn encoderEventProbe(GstPad *, GstPadProbeInfo *info, gpointer user_data); + + class EncoderProbe : public QGstreamerBufferProbe + { + public: + EncoderProbe(CameraBinImageCapture *capture) : capture(capture) {} + void probeCaps(GstCaps *caps) override; + bool probeBuffer(GstBuffer *buffer) override; + + private: + CameraBinImageCapture * const capture; + } m_encoderProbe; + + class MuxerProbe : public QGstreamerBufferProbe + { + public: + MuxerProbe(CameraBinImageCapture *capture) : capture(capture) {} + void probeCaps(GstCaps *caps) override; + bool probeBuffer(GstBuffer *buffer) override; + + private: + CameraBinImageCapture * const capture; + + } m_muxerProbe; + + QVideoSurfaceFormat m_bufferFormat; + QSize m_jpegResolution; + CameraBinSession *m_session; + GstElement *m_jpegEncoderElement; + GstElement *m_metadataMuxerElement; + GstVideoInfo m_videoInfo; + int m_requestId; + bool m_ready; + QCameraImageCapture::CaptureDestinations m_destination; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCAPTURECORNTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder.cpp new file mode 100644 index 000000000..8c4eaec70 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 "camerabinimageencoder.h" +#include "camerabinsession.h" + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +CameraBinImageEncoder::CameraBinImageEncoder(CameraBinSession *session) + :QImageEncoderControl(session), m_session(session) +{ +} + +CameraBinImageEncoder::~CameraBinImageEncoder() +{ +} + +QList<QSize> CameraBinImageEncoder::supportedResolutions(const QImageEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = false; + + return m_session->supportedResolutions(qMakePair<int,int>(0,0), continuous, QCamera::CaptureStillImage); +} + +QStringList CameraBinImageEncoder::supportedImageCodecs() const +{ + return QStringList() << "jpeg"; +} + +QString CameraBinImageEncoder::imageCodecDescription(const QString &codecName) const +{ + if (codecName == "jpeg") + return tr("JPEG image"); + + return QString(); +} + +QImageEncoderSettings CameraBinImageEncoder::imageSettings() const +{ + return m_settings; +} + +void CameraBinImageEncoder::setImageSettings(const QImageEncoderSettings &settings) +{ + m_settings = settings; + emit settingsChanged(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder_p.h new file mode 100644 index 000000000..217eb1f22 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimageencoder_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef CAMERABINIMAGEENCODE_H +#define CAMERABINIMAGEENCODE_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> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> + +#include <gst/gst.h> +QT_BEGIN_NAMESPACE + +class CameraBinSession; + +class CameraBinImageEncoder : public QImageEncoderControl +{ + Q_OBJECT +public: + CameraBinImageEncoder(CameraBinSession *session); + virtual ~CameraBinImageEncoder(); + + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings = QImageEncoderSettings(), + bool *continuous = 0) const override; + + QStringList supportedImageCodecs() const override; + QString imageCodecDescription(const QString &formatName) const override; + + QImageEncoderSettings imageSettings() const override; + void setImageSettings(const QImageEncoderSettings &settings) override; + +Q_SIGNALS: + void settingsChanged(); + +private: + QImageEncoderSettings m_settings; + + CameraBinSession *m_session; + + // Added + QStringList m_codecs; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QStringList> m_codecOptions; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing.cpp new file mode 100644 index 000000000..3629a1336 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing.cpp @@ -0,0 +1,409 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabinimageprocessing.h" +#include "camerabinsession.h" + +#if QT_CONFIG(linux_v4l) +#include "camerabinv4limageprocessing.h" +#endif + +# include <gst/video/colorbalance.h> + +QT_BEGIN_NAMESPACE + +CameraBinImageProcessing::CameraBinImageProcessing(CameraBinSession *session) + : QCameraImageProcessingControl(session) + , m_session(session) + , m_whiteBalanceMode(QCameraImageProcessing::WhiteBalanceAuto) +#if QT_CONFIG(linux_v4l) + , m_v4lImageControl(nullptr) +#endif +{ +#if QT_CONFIG(gstreamer_photography) + if (m_session->photography()) { + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_AUTO] = QCameraImageProcessing::WhiteBalanceAuto; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT] = QCameraImageProcessing::WhiteBalanceSunlight; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_CLOUDY] = QCameraImageProcessing::WhiteBalanceCloudy; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_SUNSET] = QCameraImageProcessing::WhiteBalanceSunset; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN] = QCameraImageProcessing::WhiteBalanceTungsten; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT] = QCameraImageProcessing::WhiteBalanceFluorescent; + unlockWhiteBalance(); + } + + m_filterMap.insert(QCameraImageProcessing::ColorFilterNone, GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL); + if (m_session->photography()) { + m_filterMap.insert(QCameraImageProcessing::ColorFilterSepia, GST_PHOTOGRAPHY_COLOR_TONE_MODE_SEPIA); + m_filterMap.insert(QCameraImageProcessing::ColorFilterGrayscale, GST_PHOTOGRAPHY_COLOR_TONE_MODE_GRAYSCALE); + m_filterMap.insert(QCameraImageProcessing::ColorFilterNegative, GST_PHOTOGRAPHY_COLOR_TONE_MODE_NEGATIVE); + m_filterMap.insert(QCameraImageProcessing::ColorFilterSolarize, GST_PHOTOGRAPHY_COLOR_TONE_MODE_SOLARIZE); + m_filterMap.insert(QCameraImageProcessing::ColorFilterPosterize, GST_PHOTOGRAPHY_COLOR_TONE_MODE_POSTERIZE); + m_filterMap.insert(QCameraImageProcessing::ColorFilterWhiteboard, GST_PHOTOGRAPHY_COLOR_TONE_MODE_WHITEBOARD); + m_filterMap.insert(QCameraImageProcessing::ColorFilterBlackboard, GST_PHOTOGRAPHY_COLOR_TONE_MODE_BLACKBOARD); + m_filterMap.insert(QCameraImageProcessing::ColorFilterAqua, GST_PHOTOGRAPHY_COLOR_TONE_MODE_AQUA); + } +#endif + +#if QT_CONFIG(linux_v4l) + m_v4lImageControl = new CameraBinV4LImageProcessing(m_session); + connect(m_session, &CameraBinSession::statusChanged, + m_v4lImageControl, &CameraBinV4LImageProcessing::updateParametersInfo); +#endif + + updateColorBalanceValues(); +} + +CameraBinImageProcessing::~CameraBinImageProcessing() +{ +} + +void CameraBinImageProcessing::updateColorBalanceValues() +{ + if (!GST_IS_COLOR_BALANCE(m_session->cameraBin())) { + // Camerabin doesn't implement gstcolorbalance interface + return; + } + + GstColorBalance *balance = GST_COLOR_BALANCE(m_session->cameraBin()); + const GList *controls = gst_color_balance_list_channels(balance); + + const GList *item; + GstColorBalanceChannel *channel; + gint cur_value; + qreal scaledValue = 0; + + for (item = controls; item; item = g_list_next (item)) { + channel = (GstColorBalanceChannel *)item->data; + cur_value = gst_color_balance_get_value (balance, channel); + + //map the [min_value..max_value] range to [-1.0 .. 1.0] + if (channel->min_value != channel->max_value) { + scaledValue = qreal(cur_value - channel->min_value) / + (channel->max_value - channel->min_value) * 2 - 1; + } + + if (!g_ascii_strcasecmp (channel->label, "brightness")) { + m_values[QCameraImageProcessingControl::BrightnessAdjustment] = scaledValue; + } else if (!g_ascii_strcasecmp (channel->label, "contrast")) { + m_values[QCameraImageProcessingControl::ContrastAdjustment] = scaledValue; + } else if (!g_ascii_strcasecmp (channel->label, "saturation")) { + m_values[QCameraImageProcessingControl::SaturationAdjustment] = scaledValue; + } + } +} + +bool CameraBinImageProcessing::setColorBalanceValue(const QString& channel, qreal value) +{ + + if (!GST_IS_COLOR_BALANCE(m_session->cameraBin())) { + // Camerabin doesn't implement gstcolorbalance interface + return false; + } + + GstColorBalance *balance = GST_COLOR_BALANCE(m_session->cameraBin()); + const GList *controls = gst_color_balance_list_channels(balance); + + const GList *item; + GstColorBalanceChannel *colorBalanceChannel; + + for (item = controls; item; item = g_list_next (item)) { + colorBalanceChannel = (GstColorBalanceChannel *)item->data; + + if (!g_ascii_strcasecmp (colorBalanceChannel->label, channel.toLatin1())) { + //map the [-1.0 .. 1.0] range to [min_value..max_value] + gint scaledValue = colorBalanceChannel->min_value + qRound( + (value+1.0)/2.0 * (colorBalanceChannel->max_value - colorBalanceChannel->min_value)); + + gst_color_balance_set_value (balance, colorBalanceChannel, scaledValue); + return true; + } + } + + return false; +} + +QCameraImageProcessing::WhiteBalanceMode CameraBinImageProcessing::whiteBalanceMode() const +{ + return m_whiteBalanceMode; +} + +bool CameraBinImageProcessing::setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode mode) +{ +#if QT_CONFIG(gstreamer_photography) + if (isWhiteBalanceModeSupported(mode)) { + m_whiteBalanceMode = mode; + GstPhotographyWhiteBalanceMode currentMode; + if (gst_photography_get_white_balance_mode(m_session->photography(), ¤tMode) + && currentMode != GST_PHOTOGRAPHY_WB_MODE_MANUAL) + { + unlockWhiteBalance(); + return true; + } + } +#else + Q_UNUSED(mode); +#endif + return false; +} + +bool CameraBinImageProcessing::isWhiteBalanceModeSupported(QCameraImageProcessing::WhiteBalanceMode mode) const +{ +#if QT_CONFIG(gstreamer_photography) + return m_mappedWbValues.values().contains(mode); +#else + Q_UNUSED(mode); + return false; +#endif +} + +bool CameraBinImageProcessing::isParameterSupported(QCameraImageProcessingControl::ProcessingParameter parameter) const +{ +#if QT_CONFIG(gstreamer_photography) + if (parameter == QCameraImageProcessingControl::WhiteBalancePreset + || parameter == QCameraImageProcessingControl::ColorFilter) { + if (m_session->photography()) + return true; + } +#endif + + if (parameter == QCameraImageProcessingControl::Contrast + || parameter == QCameraImageProcessingControl::Brightness + || parameter == QCameraImageProcessingControl::Saturation) { + if (GST_IS_COLOR_BALANCE(m_session->cameraBin())) + return true; + } + +#if QT_CONFIG(linux_v4l) + if (m_v4lImageControl->isParameterSupported(parameter)) + return true; +#endif + + return false; +} + +bool CameraBinImageProcessing::isParameterValueSupported(QCameraImageProcessingControl::ProcessingParameter parameter, const QVariant &value) const +{ + switch (parameter) { + case ContrastAdjustment: + case BrightnessAdjustment: + case SaturationAdjustment: { + const bool isGstColorBalanceValueSupported = GST_IS_COLOR_BALANCE(m_session->cameraBin()) + && qAbs(value.toReal()) <= 1.0; +#if QT_CONFIG(linux_v4l) + if (!isGstColorBalanceValueSupported) + return m_v4lImageControl->isParameterValueSupported(parameter, value); +#endif + return isGstColorBalanceValueSupported; + } + case SharpeningAdjustment: { +#if QT_CONFIG(linux_v4l) + return m_v4lImageControl->isParameterValueSupported(parameter, value); +#else + return false; +#endif + } + case WhiteBalancePreset: { + const QCameraImageProcessing::WhiteBalanceMode mode = + value.value<QCameraImageProcessing::WhiteBalanceMode>(); + const bool isPhotographyWhiteBalanceSupported = isWhiteBalanceModeSupported(mode); +#if QT_CONFIG(linux_v4l) + if (!isPhotographyWhiteBalanceSupported) + return m_v4lImageControl->isParameterValueSupported(parameter, value); +#endif + return isPhotographyWhiteBalanceSupported; + } + case ColorTemperature: { +#if QT_CONFIG(linux_v4l) + return m_v4lImageControl->isParameterValueSupported(parameter, value); +#else + return false; +#endif + } + case ColorFilter: { + const QCameraImageProcessing::ColorFilter filter = value.value<QCameraImageProcessing::ColorFilter>(); +#if QT_CONFIG(gstreamer_photography) + return m_filterMap.contains(filter); +#else + return filter == QCameraImageProcessing::ColorFilterNone; +#endif + } + default: + break; + } + + return false; +} + +QVariant CameraBinImageProcessing::parameter( + QCameraImageProcessingControl::ProcessingParameter parameter) const +{ + switch (parameter) { + case QCameraImageProcessingControl::WhiteBalancePreset: { + const QCameraImageProcessing::WhiteBalanceMode mode = whiteBalanceMode(); +#if QT_CONFIG(linux_v4l) + if (mode == QCameraImageProcessing::WhiteBalanceAuto + || mode == QCameraImageProcessing::WhiteBalanceManual) { + return m_v4lImageControl->parameter(parameter); + } +#endif + return QVariant::fromValue<QCameraImageProcessing::WhiteBalanceMode>(mode); + } + case QCameraImageProcessingControl::ColorTemperature: { +#if QT_CONFIG(linux_v4l) + return m_v4lImageControl->parameter(parameter); +#else + return QVariant(); +#endif + } + case QCameraImageProcessingControl::ColorFilter: +#if QT_CONFIG(gstreamer_photography) + if (GstPhotography *photography = m_session->photography()) { + GstPhotographyColorToneMode mode = GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL; + gst_photography_get_color_tone_mode(photography, &mode); + return QVariant::fromValue(m_filterMap.key(mode, QCameraImageProcessing::ColorFilterNone)); + } +#endif + return QVariant::fromValue(QCameraImageProcessing::ColorFilterNone); + default: { + const bool isGstParameterSupported = m_values.contains(parameter); +#if QT_CONFIG(linux_v4l) + if (!isGstParameterSupported) { + if (parameter == QCameraImageProcessingControl::BrightnessAdjustment + || parameter == QCameraImageProcessingControl::ContrastAdjustment + || parameter == QCameraImageProcessingControl::SaturationAdjustment + || parameter == QCameraImageProcessingControl::SharpeningAdjustment) { + return m_v4lImageControl->parameter(parameter); + } + } +#endif + return isGstParameterSupported + ? QVariant(m_values.value(parameter)) + : QVariant(); + } + } +} + +void CameraBinImageProcessing::setParameter(QCameraImageProcessingControl::ProcessingParameter parameter, + const QVariant &value) +{ + switch (parameter) { + case ContrastAdjustment: { + if (!setColorBalanceValue("contrast", value.toReal())) { +#if QT_CONFIG(linux_v4l) + m_v4lImageControl->setParameter(parameter, value); +#endif + } + } + break; + case BrightnessAdjustment: { + if (!setColorBalanceValue("brightness", value.toReal())) { +#if QT_CONFIG(linux_v4l) + m_v4lImageControl->setParameter(parameter, value); +#endif + } + } + break; + case SaturationAdjustment: { + if (!setColorBalanceValue("saturation", value.toReal())) { +#if QT_CONFIG(linux_v4l) + m_v4lImageControl->setParameter(parameter, value); +#endif + } + } + break; + case SharpeningAdjustment: { +#if QT_CONFIG(linux_v4l) + m_v4lImageControl->setParameter(parameter, value); +#endif + } + break; + case WhiteBalancePreset: { + if (!setWhiteBalanceMode(value.value<QCameraImageProcessing::WhiteBalanceMode>())) { +#if QT_CONFIG(linux_v4l) + const QCameraImageProcessing::WhiteBalanceMode mode = + value.value<QCameraImageProcessing::WhiteBalanceMode>(); + if (mode == QCameraImageProcessing::WhiteBalanceAuto + || mode == QCameraImageProcessing::WhiteBalanceManual) { + m_v4lImageControl->setParameter(parameter, value); + return; + } +#endif + } + } + break; + case QCameraImageProcessingControl::ColorTemperature: { +#if QT_CONFIG(linux_v4l) + m_v4lImageControl->setParameter(parameter, value); +#endif + break; + } + case QCameraImageProcessingControl::ColorFilter: +#if QT_CONFIG(gstreamer_photography) + if (GstPhotography *photography = m_session->photography()) { + gst_photography_set_color_tone_mode(photography, m_filterMap.value( + value.value<QCameraImageProcessing::ColorFilter>(), + GST_PHOTOGRAPHY_COLOR_TONE_MODE_NORMAL)); + } +#endif + break; + default: + break; + } + + updateColorBalanceValues(); +} + +#if QT_CONFIG(gstreamer_photography) +void CameraBinImageProcessing::lockWhiteBalance() +{ + if (GstPhotography *photography = m_session->photography()) + gst_photography_set_white_balance_mode(photography, GST_PHOTOGRAPHY_WB_MODE_MANUAL); +} + +void CameraBinImageProcessing::unlockWhiteBalance() +{ + if (GstPhotography *photography = m_session->photography()) { + gst_photography_set_white_balance_mode( + photography, m_mappedWbValues.key(m_whiteBalanceMode)); + } +} +#endif + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing_p.h new file mode 100644 index 000000000..2c5c41415 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinimageprocessing_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef CAMERABINIMAGEPROCESSINGCONTROL_H +#define CAMERABINIMAGEPROCESSINGCONTROL_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qcamera.h> +#include <qcameraimageprocessingcontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +#if QT_CONFIG(gstreamer_photography) +# include <gst/interfaces/photography.h> +#endif + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(linux_v4l) +class CameraBinV4LImageProcessing; +#endif + +class CameraBinSession; + +class CameraBinImageProcessing : public QCameraImageProcessingControl +{ + Q_OBJECT + +public: + CameraBinImageProcessing(CameraBinSession *session); + virtual ~CameraBinImageProcessing(); + + QCameraImageProcessing::WhiteBalanceMode whiteBalanceMode() const; + bool setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode mode); + bool isWhiteBalanceModeSupported(QCameraImageProcessing::WhiteBalanceMode mode) const; + + 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; + +#if QT_CONFIG(gstreamer_photography) + void lockWhiteBalance(); + void unlockWhiteBalance(); +#endif + +private: + bool setColorBalanceValue(const QString& channel, qreal value); + void updateColorBalanceValues(); + +private: + CameraBinSession *m_session; + QMap<QCameraImageProcessingControl::ProcessingParameter, int> m_values; +#if QT_CONFIG(gstreamer_photography) + QMap<GstPhotographyWhiteBalanceMode, QCameraImageProcessing::WhiteBalanceMode> m_mappedWbValues; + QMap<QCameraImageProcessing::ColorFilter, GstPhotographyColorToneMode> m_filterMap; +#endif + QCameraImageProcessing::WhiteBalanceMode m_whiteBalanceMode; + +#if QT_CONFIG(linux_v4l) + CameraBinV4LImageProcessing *m_v4lImageControl; +#endif +}; + +QT_END_NAMESPACE + +#endif // CAMERABINIMAGEPROCESSINGCONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata.cpp new file mode 100644 index 000000000..e2b12aab1 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata.cpp @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** 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 "camerabinmetadata.h" + +#include <QtMultimedia/qmediametadata.h> + +#include <gst/gst.h> +#include <gst/gstversion.h> +#include <private/qgstutils_p.h> + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +namespace { + struct QGStreamerMetaDataKey + { + QString qtName; + const char *gstName; + QMetaType::Type type; + + QGStreamerMetaDataKey(const QString &qtn, const char *gstn, QMetaType::Type t) + : qtName(qtn) + , gstName(gstn) + , type(t) + { } + }; +} + +typedef QList<QGStreamerMetaDataKey> QGStreamerMetaDataKeys; +Q_GLOBAL_STATIC(QGStreamerMetaDataKeys, metadataKeys) + +static const QGStreamerMetaDataKeys *qt_gstreamerMetaDataKeys() +{ + if (metadataKeys->isEmpty()) { + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Title, GST_TAG_TITLE, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::SubTitle, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Author, 0, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Comment, GST_TAG_COMMENT, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Date, GST_TAG_DATE_TIME, QMetaType::QDateTime)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Description, GST_TAG_DESCRIPTION, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Category, 0, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Genre, GST_TAG_GENRE, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Year, 0, QMetaType::Int)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::UserRating, , QMetaType::Int)); + + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Language, GST_TAG_LANGUAGE_CODE, QMetaType::QString)); + + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Publisher, GST_TAG_ORGANIZATION, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Copyright, GST_TAG_COPYRIGHT, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::ParentalRating, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::RatingOrganisation, 0, QMetaType::QString)); + + // Media + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Size, 0, QMetaType::Int)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::MediaType, 0, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Duration, GST_TAG_DURATION, QMetaType::Int)); + + // Audio + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AudioBitRate, GST_TAG_BITRATE, QMetaType::Int)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AudioCodec, GST_TAG_AUDIO_CODEC, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::ChannelCount, 0, QMetaType::Int)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::SampleRate, 0, QMetaType::Int)); + + // Music + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AlbumTitle, GST_TAG_ALBUM, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AlbumArtist, GST_TAG_ARTIST, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::ContributingArtist, GST_TAG_PERFORMER, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Composer, GST_TAG_COMPOSER, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Conductor, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Lyrics, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Mood, 0, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::TrackNumber, GST_TAG_TRACK_NUMBER, QMetaType::Int)); + + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CoverArtUrlSmall, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CoverArtUrlLarge, 0, QMetaType::QString)); + + // Image/Video + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Resolution, 0, QMetaType::QSize)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::PixelAspectRatio, 0, QMetaType::QSize)); + + // Video + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::VideoFrameRate, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::VideoBitRate, 0, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::VideoCodec, GST_TAG_VIDEO_CODEC, QMetaType::QString)); + + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::PosterUrl, 0, QMetaType::QString)); + + // Movie + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::ChapterNumber, 0, QMetaType::Int)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Director, 0, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::LeadPerformer, GST_TAG_PERFORMER, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Writer, 0, QMetaType::QString)); + + // Photos + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CameraManufacturer, GST_TAG_DEVICE_MANUFACTURER, QMetaType::QString)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CameraModel, GST_TAG_DEVICE_MODEL, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Event, 0, QMetaType::QString)); + //metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Subject, 0, QMetaType::QString)); + + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Orientation, GST_TAG_IMAGE_ORIENTATION, QMetaType::QString)); + + // GPS + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSLatitude, GST_TAG_GEO_LOCATION_LATITUDE, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSLongitude, GST_TAG_GEO_LOCATION_LONGITUDE, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSAltitude, GST_TAG_GEO_LOCATION_ELEVATION, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSTrack, GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSSpeed, GST_TAG_GEO_LOCATION_MOVEMENT_SPEED, QMetaType::Double)); + metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::GPSImgDirection, GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION, QMetaType::Double)); + } + + return metadataKeys; +} + +CameraBinMetaData::CameraBinMetaData(QObject *parent) + :QMetaDataWriterControl(parent) +{ +} + +QVariant CameraBinMetaData::metaData(const QString &key) const +{ + if (key == QMediaMetaData::Orientation) + return QGstUtils::fromGStreamerOrientation(m_values.value(QByteArray(GST_TAG_IMAGE_ORIENTATION))); + + if (key == QMediaMetaData::GPSSpeed) { + const double metersPerSec = m_values.value(QByteArray(GST_TAG_GEO_LOCATION_MOVEMENT_SPEED)).toDouble(); + return (metersPerSec * 3600) / 1000; + } + + const auto keys = *qt_gstreamerMetaDataKeys(); + for (const QGStreamerMetaDataKey &metadataKey : keys) { + if (metadataKey.qtName == key) + return m_values.value(QByteArray::fromRawData(metadataKey.gstName, qstrlen(metadataKey.gstName))); + } + return QVariant(); +} + +void CameraBinMetaData::setMetaData(const QString &key, const QVariant &value) +{ + QVariant correctedValue = value; + if (value.isValid()) { + if (key == QMediaMetaData::Orientation) { + correctedValue = QGstUtils::toGStreamerOrientation(value); + } else if (key == QMediaMetaData::GPSSpeed) { + // kilometers per hour to meters per second. + correctedValue = (value.toDouble() * 1000) / 3600; + } + } + + const auto keys = *qt_gstreamerMetaDataKeys(); + for (const QGStreamerMetaDataKey &metadataKey : keys) { + if (metadataKey.qtName == key) { + const char *name = metadataKey.gstName; + + if (correctedValue.isValid()) { + correctedValue.convert(QMetaType(metadataKey.type)); + m_values.insert(QByteArray::fromRawData(name, qstrlen(name)), correctedValue); + } else { + m_values.remove(QByteArray::fromRawData(name, qstrlen(name))); + } + + emit QMetaDataWriterControl::metaDataChanged(); + emit metaDataChanged(m_values); + + return; + } + } +} + +QStringList CameraBinMetaData::availableMetaData() const +{ + static QMap<QByteArray, QString> keysMap; + if (keysMap.isEmpty()) { + const auto keys = *qt_gstreamerMetaDataKeys(); + for (const QGStreamerMetaDataKey &metadataKey : keys) + keysMap[QByteArray(metadataKey.gstName)] = metadataKey.qtName; + } + + QStringList res; + for (auto it = m_values.keyBegin(), end = m_values.keyEnd(); it != end; ++it) { + QString tag = keysMap.value(*it); + if (!tag.isEmpty()) + res.append(tag); + } + + return res; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata_p.h new file mode 100644 index 000000000..b4811b9ba --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinmetadata_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 CAMERABINCAPTUREMETADATACONTROL_H +#define CAMERABINCAPTUREMETADATACONTROL_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 <qmetadatawritercontrol.h> +#include <qmap.h> +#include <qvariant.h> + +QT_BEGIN_NAMESPACE + +class CameraBinMetaData : public QMetaDataWriterControl +{ + Q_OBJECT +public: + CameraBinMetaData(QObject *parent); + virtual ~CameraBinMetaData() {} + + + bool isMetaDataAvailable() const override { return true; } + bool isWritable() const override { return true; } + + QVariant metaData(const QString &key) const override; + void setMetaData(const QString &key, const QVariant &value) override; + QStringList availableMetaData() const override; + +Q_SIGNALS: + void metaDataChanged(const QMap<QByteArray, QVariant>&); + +private: + QMap<QByteArray, QVariant> m_values; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCAPTUREMETADATACONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder.cpp new file mode 100644 index 000000000..a83971226 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** 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 "camerabinrecorder.h" +#include "camerabincontrol.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabincontainer.h" +#include <QtCore/QDebug> + + +QT_BEGIN_NAMESPACE + +CameraBinRecorder::CameraBinRecorder(CameraBinSession *session) + :QMediaRecorderControl(session), + m_session(session), + m_state(QMediaRecorder::StoppedState), + m_status(QMediaRecorder::UnloadedStatus) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateStatus())); + connect(m_session, SIGNAL(pendingStateChanged(QCamera::State)), SLOT(updateStatus())); + connect(m_session, SIGNAL(busyChanged(bool)), SLOT(updateStatus())); + + connect(m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool))); +} + +CameraBinRecorder::~CameraBinRecorder() +{ +} + +QUrl CameraBinRecorder::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool CameraBinRecorder::setOutputLocation(const QUrl &sink) +{ + m_session->setOutputLocation(sink); + return true; +} + +QMediaRecorder::State CameraBinRecorder::state() const +{ + return m_state; +} + +QMediaRecorder::Status CameraBinRecorder::status() const +{ + return m_status; +} + +void CameraBinRecorder::updateStatus() +{ + QCamera::Status sessionStatus = m_session->status(); + + QMediaRecorder::State oldState = m_state; + QMediaRecorder::Status oldStatus = m_status; + + if (sessionStatus == QCamera::ActiveStatus && + m_session->captureMode().testFlag(QCamera::CaptureVideo)) { + + if (m_state == QMediaRecorder::RecordingState) { + m_status = QMediaRecorder::RecordingStatus; + } else { + m_status = m_session->isBusy() ? + QMediaRecorder::FinalizingStatus : + QMediaRecorder::LoadedStatus; + } + } else { + if (m_state == QMediaRecorder::RecordingState) { + m_state = QMediaRecorder::StoppedState; + m_session->stopVideoRecording(); + } + m_status = m_session->pendingState() == QCamera::ActiveState + && m_session->captureMode().testFlag(QCamera::CaptureVideo) + ? QMediaRecorder::LoadingStatus + : QMediaRecorder::UnloadedStatus; + } + + if (m_state != oldState) + emit stateChanged(m_state); + + if (m_status != oldStatus) + emit statusChanged(m_status); +} + +qint64 CameraBinRecorder::duration() const +{ + return m_session->duration(); +} + + +void CameraBinRecorder::applySettings() +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + CameraBinContainer *containerControl = m_session->mediaContainerControl(); + CameraBinAudioEncoder *audioEncoderControl = m_session->audioEncodeControl(); + CameraBinVideoEncoder *videoEncoderControl = m_session->videoEncodeControl(); + + containerControl->resetActualContainerFormat(); + audioEncoderControl->resetActualSettings(); + videoEncoderControl->resetActualSettings(); + + //encodebin doesn't like the encoding profile with ANY caps, + //if container and codecs are not specified, + //try to find a commonly used supported combination + if (containerControl->containerFormat().isEmpty() && + audioEncoderControl->audioSettings().codec().isEmpty() && + videoEncoderControl->videoSettings().codec().isEmpty()) { + + QList<QStringList> candidates; + + // By order of preference + + // .mp4 (h264, AAC) + candidates.append(QStringList() << "video/quicktime, variant=(string)iso" << "video/x-h264" << "audio/mpeg, mpegversion=(int)4"); + + // .mp4 (h264, AC3) + candidates.append(QStringList() << "video/quicktime, variant=(string)iso" << "video/x-h264" << "audio/x-ac3"); + + // .mp4 (h264, MP3) + candidates.append(QStringList() << "video/quicktime, variant=(string)iso" << "video/x-h264" << "audio/mpeg, mpegversion=(int)1, layer=(int)3"); + + // .mkv (h264, AAC) + candidates.append(QStringList() << "video/x-matroska" << "video/x-h264" << "audio/mpeg, mpegversion=(int)4"); + + // .mkv (h264, AC3) + candidates.append(QStringList() << "video/x-matroska" << "video/x-h264" << "audio/x-ac3"); + + // .mkv (h264, MP3) + candidates.append(QStringList() << "video/x-matroska" << "video/x-h264" << "audio/mpeg, mpegversion=(int)1, layer=(int)3"); + + // .mov (h264, AAC) + candidates.append(QStringList() << "video/quicktime" << "video/x-h264" << "audio/mpeg, mpegversion=(int)4"); + + // .mov (h264, MP3) + candidates.append(QStringList() << "video/quicktime" << "video/x-h264" << "audio/mpeg, mpegversion=(int)1, layer=(int)3"); + + // .webm (VP8, Vorbis) + candidates.append(QStringList() << "video/webm" << "video/x-vp8" << "audio/x-vorbis"); + + // .ogg (Theora, Vorbis) + candidates.append(QStringList() << "application/ogg" << "video/x-theora" << "audio/x-vorbis"); + + // .avi (DivX, MP3) + candidates.append(QStringList() << "video/x-msvideo" << "video/x-divx" << "audio/mpeg, mpegversion=(int)1, layer=(int)3"); + + for (const QStringList &candidate : qAsConst(candidates)) { + if (containerControl->supportedContainers().contains(candidate[0]) && + videoEncoderControl->supportedVideoCodecs().contains(candidate[1]) && + audioEncoderControl->supportedAudioCodecs().contains(candidate[2])) { + containerControl->setActualContainerFormat(candidate[0]); + + QVideoEncoderSettings videoSettings = videoEncoderControl->videoSettings(); + videoSettings.setCodec(candidate[1]); + videoEncoderControl->setActualVideoSettings(videoSettings); + + QAudioEncoderSettings audioSettings = audioEncoderControl->audioSettings(); + audioSettings.setCodec(candidate[2]); + audioEncoderControl->setActualAudioSettings(audioSettings); + + break; + } + } + } +#endif +} + +#if QT_CONFIG(gstreamer_encodingprofiles) + +GstEncodingContainerProfile *CameraBinRecorder::videoProfile() +{ + GstEncodingContainerProfile *containerProfile = m_session->mediaContainerControl()->createProfile(); + + if (containerProfile) { + GstEncodingProfile *audioProfile = m_session->audioEncodeControl()->createProfile(); + GstEncodingProfile *videoProfile = m_session->videoEncodeControl()->createProfile(); + + if (audioProfile) { + if (!gst_encoding_container_profile_add_profile(containerProfile, audioProfile)) + gst_encoding_profile_unref(audioProfile); + } + if (videoProfile) { + if (!gst_encoding_container_profile_add_profile(containerProfile, videoProfile)) + gst_encoding_profile_unref(videoProfile); + } + } + + return containerProfile; +} + +#endif + +void CameraBinRecorder::setState(QMediaRecorder::State state) +{ + if (m_state == state) + return; + + QMediaRecorder::State oldState = m_state; + QMediaRecorder::Status oldStatus = m_status; + + switch (state) { + case QMediaRecorder::StoppedState: + m_state = state; + m_status = QMediaRecorder::FinalizingStatus; + m_session->stopVideoRecording(); + break; + case QMediaRecorder::PausedState: + emit error(QMediaRecorder::ResourceError, tr("QMediaRecorder::pause() is not supported by camerabin2.")); + break; + case QMediaRecorder::RecordingState: + + if (m_session->status() != QCamera::ActiveStatus) { + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); + } else { + m_session->recordVideo(); + m_state = state; + m_status = QMediaRecorder::RecordingStatus; + emit actualLocationChanged(m_session->outputLocation()); + } + } + + if (m_state != oldState) + emit stateChanged(m_state); + + if (m_status != oldStatus) + emit statusChanged(m_status); +} + +bool CameraBinRecorder::isMuted() const +{ + return m_session->isMuted(); +} + +qreal CameraBinRecorder::volume() const +{ + return 1.0; +} + +void CameraBinRecorder::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +void CameraBinRecorder::setVolume(qreal volume) +{ + if (!qFuzzyCompare(volume, qreal(1.0))) + qWarning() << "Media service doesn't support recorder audio gain."; +} + +QT_END_NAMESPACE + diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder_p.h new file mode 100644 index 000000000..58a5ee725 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinrecorder_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 CAMERABINRECORDERCONTROL_H +#define CAMERABINRECORDERCONTROL_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qmediarecordercontrol.h> +#include "camerabinsession.h" + +#if QT_CONFIG(gstreamer_encodingprofiles) +#include <gst/pbutils/encoding-profile.h> +#endif + +QT_BEGIN_NAMESPACE + +class CameraBinRecorder : public QMediaRecorderControl +{ + Q_OBJECT + +public: + CameraBinRecorder(CameraBinSession *session); + virtual ~CameraBinRecorder(); + + QUrl outputLocation() const override; + bool setOutputLocation(const QUrl &sink) 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; + +#if QT_CONFIG(gstreamer_encodingprofiles) + GstEncodingContainerProfile *videoProfile(); +#endif + +public slots: + void setState(QMediaRecorder::State state) override; + void setMuted(bool) override; + void setVolume(qreal volume) override; + + void updateStatus(); + +private: + CameraBinSession *m_session; + QMediaRecorder::State m_state; + QMediaRecorder::Status m_status; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCAPTURECORNTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinservice.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinservice.cpp new file mode 100644 index 000000000..9eee847e0 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinservice.cpp @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabinservice.h" +#include "camerabinsession.h" +#include "camerabinrecorder.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" +#include "camerabincontrol.h" +#include "camerabinmetadata.h" + +#if QT_CONFIG(gstreamer_photography) +#include "camerabinexposure.h" +#include "camerabinfocus.h" +#endif + +#include "camerabinimagecapture.h" +#include "camerabinimageprocessing.h" +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstutils_p.h> + +#include <private/qgstreameraudioinputselector_p.h> +#include <private/qgstreamervideoinputdevicecontrol_p.h> + +#include <private/qgstreamervideowindow_p.h> +#include <private/qgstreamervideorenderer_p.h> +#include <private/qmediaserviceprovider_p.h> + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +CameraBinService::CameraBinService(GstElementFactory *sourceFactory, QObject *parent) + : QMediaService(parent) +{ + m_captureSession = 0; + m_metaDataControl = 0; + + m_audioInputSelector = 0; + m_videoInputDevice = 0; + + m_videoOutput = 0; + m_videoRenderer = 0; + m_videoWindow = 0; + m_imageCaptureControl = 0; + + m_captureSession = new CameraBinSession(sourceFactory, this); + m_videoInputDevice = new QGstreamerVideoInputDeviceControl(sourceFactory, m_captureSession); + m_imageCaptureControl = new CameraBinImageCapture(m_captureSession); + + connect(m_videoInputDevice, SIGNAL(selectedDeviceChanged(QString)), + m_captureSession, SLOT(setDevice(QString))); + + if (m_videoInputDevice->deviceCount()) + m_captureSession->setDevice(m_videoInputDevice->deviceName(m_videoInputDevice->selectedDevice())); + + m_videoRenderer = new QGstreamerVideoRenderer(this); + + m_videoWindow = new QGstreamerVideoWindow(this); + // If the GStreamer video sink is not available, don't provide the video window control since + // it won't work anyway. + if (!m_videoWindow->videoSink()) { + delete m_videoWindow; + m_videoWindow = 0; + } + + m_audioInputSelector = new QGstreamerAudioInputSelector(this); + connect(m_audioInputSelector, SIGNAL(activeInputChanged(QString)), m_captureSession, SLOT(setCaptureDevice(QString))); + + if (m_captureSession && m_audioInputSelector->availableInputs().size() > 0) + m_captureSession->setCaptureDevice(m_audioInputSelector->defaultInput()); + + m_metaDataControl = new CameraBinMetaData(this); + connect(m_metaDataControl, SIGNAL(metaDataChanged(QMap<QByteArray,QVariant>)), + m_captureSession, SLOT(setMetaData(QMap<QByteArray,QVariant>))); +} + +CameraBinService::~CameraBinService() +{ +} + +QObject *CameraBinService::requestControl(const char *name) +{ + if (!m_captureSession) + return 0; + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + m_videoOutput = m_videoRenderer; + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + m_videoOutput = m_videoWindow; + } + + if (m_videoOutput) { + m_captureSession->setViewfinder(m_videoOutput); + return m_videoOutput; + } + } + + if (qstrcmp(name, QMediaVideoProbeControl_iid) == 0) + return m_captureSession->videoProbe(); + + if (qstrcmp(name,QAudioInputSelectorControl_iid) == 0) + return m_audioInputSelector; + + if (qstrcmp(name,QVideoDeviceSelectorControl_iid) == 0) + return m_videoInputDevice; + + if (qstrcmp(name,QMediaRecorderControl_iid) == 0) + return m_captureSession->recorderControl(); + + if (qstrcmp(name,QAudioEncoderSettingsControl_iid) == 0) + return m_captureSession->audioEncodeControl(); + + if (qstrcmp(name,QVideoEncoderSettingsControl_iid) == 0) + return m_captureSession->videoEncodeControl(); + + if (qstrcmp(name,QImageEncoderControl_iid) == 0) + return m_captureSession->imageEncodeControl(); + + + if (qstrcmp(name,QMediaContainerControl_iid) == 0) + return m_captureSession->mediaContainerControl(); + + if (qstrcmp(name,QCameraControl_iid) == 0) + return m_captureSession->cameraControl(); + + if (qstrcmp(name,QMetaDataWriterControl_iid) == 0) + return m_metaDataControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + +#if QT_CONFIG(gstreamer_photography) + if (qstrcmp(name, QCameraExposureControl_iid) == 0) + return m_captureSession->cameraExposureControl(); + + if (qstrcmp(name, QCameraFocusControl_iid) == 0) + return m_captureSession->cameraFocusControl(); +#endif + + if (qstrcmp(name, QCameraImageProcessingControl_iid) == 0) + return m_captureSession->imageProcessingControl(); + + return nullptr; +} + +void CameraBinService::releaseControl(QObject *control) +{ + if (control && control == m_videoOutput) { + m_videoOutput = 0; + m_captureSession->setViewfinder(0); + } +} + +bool CameraBinService::isCameraBinAvailable() +{ + GstElementFactory *factory = gst_element_factory_find("camerabin"); + if (factory) { + gst_object_unref(GST_OBJECT(factory)); + return true; + } + + return false; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinservice_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinservice_p.h new file mode 100644 index 000000000..24d388759 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinservice_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 CAMERABINCAPTURESERVICE_H +#define CAMERABINCAPTURESERVICE_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> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE +class QAudioInputSelectorControl; +class QVideoDeviceSelectorControl; + + +class CameraBinSession; +class CameraBinControl; +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerVideoRenderer; +class QGstreamerVideoWindow; +class QGstreamerElementFactory; +class CameraBinMetaData; +class CameraBinImageCapture; +class CameraBinMetaData; + +class CameraBinService : public QMediaService +{ + Q_OBJECT + +public: + CameraBinService(GstElementFactory *sourceFactory, QObject *parent = 0); + virtual ~CameraBinService(); + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *) override; + + static bool isCameraBinAvailable(); + +private: + void setAudioPreview(GstElement*); + + CameraBinSession *m_captureSession; + CameraBinMetaData *m_metaDataControl; + + QAudioInputSelectorControl *m_audioInputSelector; + QVideoDeviceSelectorControl *m_videoInputDevice; + + QObject *m_videoOutput; + + QObject *m_videoRenderer; + QGstreamerVideoWindow *m_videoWindow; + CameraBinImageCapture *m_imageCaptureControl; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCAPTURESERVICE_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin.cpp new file mode 100644 index 000000000..f8d9b5a37 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** 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 <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include "camerabinserviceplugin.h" + +#include "camerabinservice.h" +#include <private/qgstutils_p.h> + +QT_BEGIN_NAMESPACE + +template <typename T, int N> static int lengthOf(const T(&)[N]) { return N; } + +CameraBinServicePlugin::CameraBinServicePlugin() + : m_sourceFactory(0) +{ +} + +CameraBinServicePlugin::~CameraBinServicePlugin() +{ + if (m_sourceFactory) + gst_object_unref(GST_OBJECT(m_sourceFactory)); +} + +QMediaService* CameraBinServicePlugin::create(const QString &key) +{ + QGstUtils::initializeGst(); + + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA)) { + if (!CameraBinService::isCameraBinAvailable()) { + guint major, minor, micro, nano; + gst_version(&major, &minor, µ, &nano); + qWarning("Error: cannot create camera service, the 'camerabin' plugin is missing for " + "GStreamer %u.%u." + "\nPlease install the 'bad' GStreamer plugin package.", + major, minor); + return nullptr; + } + + return new CameraBinService(sourceFactory()); + } + + qWarning() << "Gstreamer camerabin service plugin: unsupported key:" << key; + return 0; +} + +void CameraBinServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QByteArray CameraBinServicePlugin::defaultDevice(const QByteArray &service) const +{ + return service == Q_MEDIASERVICE_CAMERA + ? QGstUtils::enumerateCameras().value(0).name.toUtf8() + : QByteArray(); +} + +QList<QByteArray> CameraBinServicePlugin::devices(const QByteArray &service) const +{ + + return service == Q_MEDIASERVICE_CAMERA + ? QGstUtils::cameraDevices() + : QList<QByteArray>(); +} + +QString CameraBinServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &deviceName) +{ + return service == Q_MEDIASERVICE_CAMERA + ? QGstUtils::cameraDescription(deviceName) + : QString(); +} + +QVariant CameraBinServicePlugin::deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property) +{ + Q_UNUSED(service); + Q_UNUSED(device); + Q_UNUSED(property); + return QVariant(); +} + +GstElementFactory *CameraBinServicePlugin::sourceFactory() const +{ + if (!m_sourceFactory) { + GstElementFactory *factory = 0; + const QByteArray envCandidate = qgetenv("QT_GSTREAMER_CAMERABIN_SRC"); + if (!envCandidate.isEmpty()) + factory = gst_element_factory_find(envCandidate.constData()); + + static const char *candidates[] = { "subdevsrc", "wrappercamerabinsrc" }; + for (int i = 0; !factory && i < lengthOf(candidates); ++i) + factory = gst_element_factory_find(candidates[i]); + + if (factory) { + m_sourceFactory = GST_ELEMENT_FACTORY(gst_plugin_feature_load( + GST_PLUGIN_FEATURE(factory))); + gst_object_unref((GST_OBJECT(factory))); + } + } + + return m_sourceFactory; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin_p.h new file mode 100644 index 000000000..00de757bf --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinserviceplugin_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** 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 CAMERABINSERVICEPLUGIN_H +#define CAMERABINSERVICEPLUGIN_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.h> +#include <private/qgstreamervideoinputdevicecontrol_p.h> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class CameraBinServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedDevicesInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "camerabin.json") +public: + CameraBinServicePlugin(); + ~CameraBinServicePlugin(); + + QMediaService* create(const QString &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; + QVariant deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property); + +private: + GstElementFactory *sourceFactory() const; + + mutable GstElementFactory *m_sourceFactory; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURESERVICEPLUGIN_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinsession.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinsession.cpp new file mode 100644 index 000000000..3c05156cc --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinsession.cpp @@ -0,0 +1,1510 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabinsession.h" +#include "camerabincontrol.h" +#include "camerabinrecorder.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" + +#if QT_CONFIG(gstreamer_photography) +#include "camerabinexposure.h" +#include "camerabinfocus.h" +#endif + +#include "camerabinimageprocessing.h" + +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstreamervideorendererinterface_p.h> +#include <private/qgstutils_p.h> +#include <qmediarecorder.h> +#include <qvideosurfaceformat.h> + +#if QT_CONFIG(gstreamer_photography) +#include <gst/interfaces/photography.h> +#endif + +#include <gst/gsttagsetter.h> +#include <gst/gstversion.h> + +#include <QtCore/qdebug.h> +#include <QCoreApplication> +#include <QtCore/qmetaobject.h> +#include <QtGui/qdesktopservices.h> + +#include <QtGui/qimage.h> +#include <QtCore/qdatetime.h> + +#include <algorithm> + +//#define CAMERABIN_DEBUG 1 +//#define CAMERABIN_DEBUG_DUMP_BIN 1 +#define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + +#define FILENAME_PROPERTY "location" +#define MODE_PROPERTY "mode" +#define MUTE_PROPERTY "mute" +#define IMAGE_PP_PROPERTY "image-post-processing" +#define IMAGE_ENCODER_PROPERTY "image-encoder" +#define VIDEO_PP_PROPERTY "video-post-processing" +#define VIEWFINDER_SINK_PROPERTY "viewfinder-sink" +#define CAMERA_SOURCE_PROPERTY "camera-source" +#define AUDIO_SOURCE_PROPERTY "audio-source" +#define SUPPORTED_IMAGE_CAPTURE_CAPS_PROPERTY "image-capture-supported-caps" +#define SUPPORTED_VIDEO_CAPTURE_CAPS_PROPERTY "video-capture-supported-caps" +#define SUPPORTED_VIEWFINDER_CAPS_PROPERTY "viewfinder-supported-caps" +#define AUDIO_CAPTURE_CAPS_PROPERTY "audio-capture-caps" +#define IMAGE_CAPTURE_CAPS_PROPERTY "image-capture-caps" +#define VIDEO_CAPTURE_CAPS_PROPERTY "video-capture-caps" +#define VIEWFINDER_CAPS_PROPERTY "viewfinder-caps" +#define PREVIEW_CAPS_PROPERTY "preview-caps" +#define POST_PREVIEWS_PROPERTY "post-previews" + + +#define CAPTURE_START "start-capture" +#define CAPTURE_STOP "stop-capture" + +#define FILESINK_BIN_NAME "videobin-filesink" + +#define CAMERABIN_IMAGE_MODE 1 +#define CAMERABIN_VIDEO_MODE 2 + +#define PREVIEW_CAPS_4_3 \ + "video/x-raw-rgb, width = (int) 640, height = (int) 480" + +QT_BEGIN_NAMESPACE + +CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *parent) + :QObject(parent), + m_recordingActive(false), + m_status(QCamera::UnloadedStatus), + m_pendingState(QCamera::UnloadedState), + m_muted(false), + m_busy(false), + m_captureMode(QCamera::CaptureStillImage), + m_audioInputFactory(0), + m_videoInputFactory(0), + m_viewfinder(0), + m_viewfinderInterface(0), +#if QT_CONFIG(gstreamer_photography) + m_cameraExposureControl(0), + m_cameraFocusControl(0), +#endif + m_cameraSrc(0), + m_videoSrc(0), + m_viewfinderElement(0), + m_sourceFactory(sourceFactory), + m_viewfinderHasChanged(true), + m_inputDeviceHasChanged(true), + m_usingWrapperCameraBinSrc(false), + m_viewfinderProbe(this), + m_audioSrc(0), + m_audioConvert(0), + m_capsFilter(0), + m_fileSink(0), + m_audioEncoder(0), + m_videoEncoder(0), + m_muxer(0) +{ + if (m_sourceFactory) + gst_object_ref(GST_OBJECT(m_sourceFactory)); + m_camerabin = gst_element_factory_make("camerabin", "camerabin"); + + g_signal_connect(G_OBJECT(m_camerabin), "notify::idle", G_CALLBACK(updateBusyStatus), this); + g_signal_connect(G_OBJECT(m_camerabin), "element-added", G_CALLBACK(elementAdded), this); + g_signal_connect(G_OBJECT(m_camerabin), "element-removed", G_CALLBACK(elementRemoved), this); + qt_gst_object_ref_sink(m_camerabin); + + m_bus = gst_element_get_bus(m_camerabin); + + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installMessageFilter(this); + + m_cameraControl = new CameraBinControl(this); + m_audioEncodeControl = new CameraBinAudioEncoder(this); + m_videoEncodeControl = new CameraBinVideoEncoder(this); + m_imageEncodeControl = new CameraBinImageEncoder(this); + m_recorderControl = new CameraBinRecorder(this); + m_mediaContainerControl = new CameraBinContainer(this); + m_imageProcessingControl = new CameraBinImageProcessing(this); + + QByteArray envFlags = qgetenv("QT_GSTREAMER_CAMERABIN_FLAGS"); + if (!envFlags.isEmpty()) + g_object_set(G_OBJECT(m_camerabin), "flags", envFlags.toInt(), NULL); + + //post image preview in RGB format + g_object_set(G_OBJECT(m_camerabin), POST_PREVIEWS_PROPERTY, TRUE, NULL); + + GstCaps *previewCaps = gst_caps_new_simple( + "video/x-raw", + "format", G_TYPE_STRING, "RGBx", + NULL); + + g_object_set(G_OBJECT(m_camerabin), PREVIEW_CAPS_PROPERTY, previewCaps, NULL); + gst_caps_unref(previewCaps); +} + +CameraBinSession::~CameraBinSession() +{ + if (m_camerabin) { + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_camerabin, GST_STATE_NULL); + gst_element_get_state(m_camerabin, NULL, NULL, GST_CLOCK_TIME_NONE); + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_camerabin)); + } + if (m_viewfinderElement) + gst_object_unref(GST_OBJECT(m_viewfinderElement)); + + if (m_sourceFactory) + gst_object_unref(GST_OBJECT(m_sourceFactory)); + + if (m_cameraSrc) + gst_object_unref(GST_OBJECT(m_cameraSrc)); + + if (m_videoSrc) + gst_object_unref(GST_OBJECT(m_videoSrc)); +} + +#if QT_CONFIG(gstreamer_photography) +GstPhotography *CameraBinSession::photography() +{ + if (GST_IS_PHOTOGRAPHY(m_camerabin)) { + return GST_PHOTOGRAPHY(m_camerabin); + } + + GstElement * const source = buildCameraSource(); + + if (source && GST_IS_PHOTOGRAPHY(source)) + return GST_PHOTOGRAPHY(source); + + return 0; +} + +CameraBinExposure *CameraBinSession::cameraExposureControl() +{ + if (!m_cameraExposureControl && photography()) + m_cameraExposureControl = new CameraBinExposure(this); + return m_cameraExposureControl; +} + +CameraBinFocus *CameraBinSession::cameraFocusControl() +{ + if (!m_cameraFocusControl && photography()) + m_cameraFocusControl = new CameraBinFocus(this); + return m_cameraFocusControl; +} +#endif + +bool CameraBinSession::setupCameraBin() +{ + if (!buildCameraSource()) + return false; + + if (m_viewfinderHasChanged) { + if (m_viewfinderElement) { + GstPad *pad = gst_element_get_static_pad(m_viewfinderElement, "sink"); + m_viewfinderProbe.removeProbeFromPad(pad); + gst_object_unref(GST_OBJECT(pad)); + gst_object_unref(GST_OBJECT(m_viewfinderElement)); + } + + m_viewfinderElement = m_viewfinderInterface ? m_viewfinderInterface->videoSink() : 0; +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Viewfinder changed, reconfigure."; +#endif + m_viewfinderHasChanged = false; + if (!m_viewfinderElement) { + if (m_pendingState == QCamera::ActiveState) + qWarning() << "Starting camera without viewfinder available"; + m_viewfinderElement = gst_element_factory_make("fakesink", NULL); + } + + GstPad *pad = gst_element_get_static_pad(m_viewfinderElement, "sink"); + m_viewfinderProbe.addProbeToPad(pad); + gst_object_unref(GST_OBJECT(pad)); + + g_object_set(G_OBJECT(m_viewfinderElement), "sync", FALSE, NULL); + qt_gst_object_ref_sink(GST_OBJECT(m_viewfinderElement)); + gst_element_set_state(m_camerabin, GST_STATE_NULL); + g_object_set(G_OBJECT(m_camerabin), VIEWFINDER_SINK_PROPERTY, m_viewfinderElement, NULL); + } + + return true; +} + +static GstCaps *resolutionToCaps(const QSize &resolution, + qreal frameRate = 0.0, + QVideoFrame::PixelFormat pixelFormat = QVideoFrame::Format_Invalid) +{ + GstCaps *caps = 0; + if (pixelFormat == QVideoFrame::Format_Invalid) + caps = QGstUtils::videoFilterCaps(); + else + caps = QGstUtils::capsForFormats(QList<QVideoFrame::PixelFormat>() << pixelFormat); + + if (!resolution.isEmpty()) { + gst_caps_set_simple( + caps, + "width", G_TYPE_INT, resolution.width(), + "height", G_TYPE_INT, resolution.height(), + NULL); + } + + if (frameRate > 0.0) { + gint numerator; + gint denominator; + qt_gst_util_double_to_fraction(frameRate, &numerator, &denominator); + + gst_caps_set_simple( + caps, + "framerate", GST_TYPE_FRACTION, numerator, denominator, + NULL); + } + + return caps; +} + +void CameraBinSession::setupCaptureResolution() +{ + QSize viewfinderResolution = m_viewfinderSettings.resolution(); + qreal viewfinderFrameRate = m_viewfinderSettings.maximumFrameRate(); + QVideoFrame::PixelFormat viewfinderPixelFormat = m_viewfinderSettings.pixelFormat(); + const QSize imageResolution = m_imageEncodeControl->imageSettings().resolution(); + const QSize videoResolution = m_videoEncodeControl->actualVideoSettings().resolution(); + + // WrapperCameraBinSrc cannot have different caps on its imgsrc, vidsrc and vfsrc pads. + // If capture resolution is specified, use it also for the viewfinder to avoid caps negotiation + // to fail. + if (m_usingWrapperCameraBinSrc) { + if (viewfinderResolution.isEmpty()) { + if (m_captureMode == QCamera::CaptureStillImage && !imageResolution.isEmpty()) + viewfinderResolution = imageResolution; + else if (m_captureMode == QCamera::CaptureVideo && !videoResolution.isEmpty()) + viewfinderResolution = videoResolution; + } + + // Make sure we don't use incompatible frame rate and pixel format with the new resolution + if (viewfinderResolution != m_viewfinderSettings.resolution() && + (!qFuzzyIsNull(viewfinderFrameRate) || viewfinderPixelFormat != QVideoFrame::Format_Invalid)) { + + enum { + Nothing = 0x0, + OnlyFrameRate = 0x1, + OnlyPixelFormat = 0x2, + Both = 0x4 + }; + quint8 found = Nothing; + auto viewfinderSettings = supportedViewfinderSettings(); + for (int i = 0; i < viewfinderSettings.count() && !(found & Both); ++i) { + const QCameraViewfinderSettings &s = viewfinderSettings.at(i); + if (s.resolution() == viewfinderResolution) { + if ((qFuzzyIsNull(viewfinderFrameRate) || s.maximumFrameRate() == viewfinderFrameRate) + && (viewfinderPixelFormat == QVideoFrame::Format_Invalid || s.pixelFormat() == viewfinderPixelFormat)) + found |= Both; + else if (s.maximumFrameRate() == viewfinderFrameRate) + found |= OnlyFrameRate; + else if (s.pixelFormat() == viewfinderPixelFormat) + found |= OnlyPixelFormat; + } + } + + if (found & Both) { + // no-op + } else if (found & OnlyPixelFormat) { + viewfinderFrameRate = qreal(0); + } else if (found & OnlyFrameRate) { + viewfinderPixelFormat = QVideoFrame::Format_Invalid; + } else { + viewfinderPixelFormat = QVideoFrame::Format_Invalid; + viewfinderFrameRate = qreal(0); + } + } + } + + GstCaps *caps = resolutionToCaps(imageResolution); + g_object_set(m_camerabin, IMAGE_CAPTURE_CAPS_PROPERTY, caps, NULL); + gst_caps_unref(caps); + + qreal framerate = m_videoEncodeControl->videoSettings().frameRate(); + caps = resolutionToCaps(videoResolution, framerate); + g_object_set(m_camerabin, VIDEO_CAPTURE_CAPS_PROPERTY, caps, NULL); + gst_caps_unref(caps); + + caps = resolutionToCaps(viewfinderResolution, viewfinderFrameRate, viewfinderPixelFormat); + g_object_set(m_camerabin, VIEWFINDER_CAPS_PROPERTY, caps, NULL); + gst_caps_unref(caps); + + // Special case when using mfw_v4lsrc + if (m_videoSrc && qstrcmp(qt_gst_element_get_factory_name(m_videoSrc), "mfw_v4lsrc") == 0) { + int capMode = 0; + if (viewfinderResolution == QSize(320, 240)) + capMode = 1; + else if (viewfinderResolution == QSize(720, 480)) + capMode = 2; + else if (viewfinderResolution == QSize(720, 576)) + capMode = 3; + else if (viewfinderResolution == QSize(1280, 720)) + capMode = 4; + else if (viewfinderResolution == QSize(1920, 1080)) + capMode = 5; + g_object_set(G_OBJECT(m_videoSrc), "capture-mode", capMode, NULL); + + if (!qFuzzyIsNull(viewfinderFrameRate)) { + int n, d; + qt_gst_util_double_to_fraction(viewfinderFrameRate, &n, &d); + g_object_set(G_OBJECT(m_videoSrc), "fps-n", n, NULL); + g_object_set(G_OBJECT(m_videoSrc), "fps-d", d, NULL); + } + } + + if (m_videoEncoder) + m_videoEncodeControl->applySettings(m_videoEncoder); +} + +void CameraBinSession::setAudioCaptureCaps() +{ + QAudioEncoderSettings settings = m_audioEncodeControl->audioSettings(); + const int sampleRate = settings.sampleRate(); + const int channelCount = settings.channelCount(); + + if (sampleRate <= 0 && channelCount <=0) + return; + + GstStructure *structure = gst_structure_new_empty("audio/x-raw"); + if (sampleRate > 0) + gst_structure_set(structure, "rate", G_TYPE_INT, sampleRate, NULL); + if (channelCount > 0) + gst_structure_set(structure, "channels", G_TYPE_INT, channelCount, NULL); + + GstCaps *caps = gst_caps_new_full(structure, NULL); + g_object_set(G_OBJECT(m_camerabin), AUDIO_CAPTURE_CAPS_PROPERTY, caps, NULL); + gst_caps_unref(caps); + + if (m_audioEncoder) + m_audioEncodeControl->applySettings(m_audioEncoder); +} + +GstElement *CameraBinSession::buildCameraSource() +{ +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO; +#endif + if (m_inputDevice.isEmpty()) + return nullptr; + + if (!m_inputDeviceHasChanged) + return m_cameraSrc; + + m_inputDeviceHasChanged = false; + m_usingWrapperCameraBinSrc = false; + + GstElement *camSrc = 0; + g_object_get(G_OBJECT(m_camerabin), CAMERA_SOURCE_PROPERTY, &camSrc, NULL); + + if (!m_cameraSrc && m_sourceFactory) + m_cameraSrc = gst_element_factory_create(m_sourceFactory, "camera_source"); + + // If gstreamer has set a default source use it. + if (!m_cameraSrc) + m_cameraSrc = camSrc; + + if (m_cameraSrc) { +#if CAMERABIN_DEBUG + qDebug() << "set camera device" << m_inputDevice; +#endif + m_usingWrapperCameraBinSrc = qstrcmp(qt_gst_element_get_factory_name(m_cameraSrc), "wrappercamerabinsrc") == 0; + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_cameraSrc), "video-source")) { + if (!m_videoSrc) { + /* QT_GSTREAMER_CAMERABIN_VIDEOSRC can be used to set the video source element. + + --- Usage + + QT_GSTREAMER_CAMERABIN_VIDEOSRC=[drivername=elementname[,drivername2=elementname2 ...],][elementname] + + --- Examples + + Always use 'somevideosrc': + QT_GSTREAMER_CAMERABIN_VIDEOSRC="somevideosrc" + + Use 'somevideosrc' when the device driver is 'somedriver', otherwise use default: + QT_GSTREAMER_CAMERABIN_VIDEOSRC="somedriver=somevideosrc" + + Use 'somevideosrc' when the device driver is 'somedriver', otherwise use 'somevideosrc2' + QT_GSTREAMER_CAMERABIN_VIDEOSRC="somedriver=somevideosrc,somevideosrc2" + */ + const QByteArray envVideoSource = qgetenv("QT_GSTREAMER_CAMERABIN_VIDEOSRC"); + + if (!envVideoSource.isEmpty()) { + const QList<QByteArray> sources = envVideoSource.split(','); + for (const QByteArray &source : sources) { + QList<QByteArray> keyValue = source.split('='); + QByteArray name = keyValue.at(0); + if (keyValue.count() > 1 && keyValue.at(0) == QGstUtils::cameraDriver(m_inputDevice)) + name = keyValue.at(1); + + GError *error = NULL; + GstElement *element = gst_parse_launch(name, &error); + + if (error) { + g_printerr("ERROR: %s: %s\n", name.constData(), GST_STR_NULL(error->message)); + g_clear_error(&error); + } + if (element) { + m_videoSrc = element; + break; + } + } + } else if (m_videoInputFactory) { + m_videoSrc = m_videoInputFactory->buildElement(); + } + + if (!m_videoSrc) + m_videoSrc = gst_element_factory_make("v4l2src", "camera_source"); + + if (!m_videoSrc) + m_videoSrc = gst_element_factory_make("ksvideosrc", "camera_source"); + + if (!m_videoSrc) + m_videoSrc = gst_element_factory_make("avfvideosrc", "camera_source"); + + if (m_videoSrc) + g_object_set(G_OBJECT(m_cameraSrc), "video-source", m_videoSrc, NULL); + } + + if (m_videoSrc) { + if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSrc), "device")) + g_object_set(G_OBJECT(m_videoSrc), "device", m_inputDevice.toUtf8().constData(), NULL); + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSrc), "device-path")) + g_object_set(G_OBJECT(m_videoSrc), "device-path", m_inputDevice.toUtf8().constData(), NULL); + + if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSrc), "device-index")) + g_object_set(G_OBJECT(m_videoSrc), "device-index", m_inputDevice.toInt(), NULL); + } + } else if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_cameraSrc), "camera-device")) { + if (m_inputDevice == QLatin1String("secondary")) { + g_object_set(G_OBJECT(m_cameraSrc), "camera-device", 1, NULL); + } else { + g_object_set(G_OBJECT(m_cameraSrc), "camera-device", 0, NULL); + } + } + } + + if (m_cameraSrc != camSrc) { + g_object_set(G_OBJECT(m_camerabin), CAMERA_SOURCE_PROPERTY, m_cameraSrc, NULL); + // Unref only if camSrc is not m_cameraSrc to prevent double unrefing. + if (camSrc) + gst_object_unref(GST_OBJECT(camSrc)); + } + + return m_cameraSrc; +} + +void CameraBinSession::captureImage(int requestId, const QString &fileName) +{ + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QMediaStorageLocation::Pictures, + QLatin1String("IMG_"), + QLatin1String("jpg")); + + m_requestId = requestId; + +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << m_requestId << fileName << "actual file name:" << actualFileName; +#endif + + g_object_set(G_OBJECT(m_camerabin), FILENAME_PROPERTY, actualFileName.toLocal8Bit().constData(), NULL); + + g_signal_emit_by_name(G_OBJECT(m_camerabin), CAPTURE_START, NULL); + + m_imageFileName = actualFileName; +} + +void CameraBinSession::setCaptureMode(QCamera::CaptureModes mode) +{ + m_captureMode = mode; + + switch (m_captureMode) { + case QCamera::CaptureStillImage: + g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_IMAGE_MODE, NULL); + break; + case QCamera::CaptureVideo: + g_object_set(m_camerabin, MODE_PROPERTY, CAMERABIN_VIDEO_MODE, NULL); + break; + } + + m_recorderControl->updateStatus(); +} + +QUrl CameraBinSession::outputLocation() const +{ + //return the location service wrote data to, not one set by user, it can be empty. + return m_actualSink; +} + +bool CameraBinSession::setOutputLocation(const QUrl& sink) +{ + if (!sink.isRelative() && !sink.isLocalFile()) { + qWarning("Output location must be a local file"); + return false; + } + + m_sink = m_actualSink = sink; + return true; +} + +void CameraBinSession::setDevice(const QString &device) +{ + if (m_inputDevice != device) { + m_inputDevice = device; + m_inputDeviceHasChanged = true; + } +} + +void CameraBinSession::setAudioInput(QGstreamerElementFactory *audioInput) +{ + m_audioInputFactory = audioInput; +} + +void CameraBinSession::setVideoInput(QGstreamerElementFactory *videoInput) +{ + m_videoInputFactory = videoInput; + m_inputDeviceHasChanged = true; +} + +bool CameraBinSession::isReady() const +{ + //it's possible to use QCamera without any viewfinder attached + return !m_viewfinderInterface || m_viewfinderInterface->isReady(); +} + +void CameraBinSession::setViewfinder(QObject *viewfinder) +{ + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + m_viewfinderInterface = qobject_cast<QGstreamerVideoRendererInterface*>(viewfinder); + if (!m_viewfinderInterface) + viewfinder = 0; + + if (m_viewfinder != viewfinder) { + bool oldReady = isReady(); + + if (m_viewfinder) { + disconnect(m_viewfinder, SIGNAL(sinkChanged()), + this, SLOT(handleViewfinderChange())); + disconnect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + + m_busHelper->removeMessageFilter(m_viewfinder); + } + + m_viewfinder = viewfinder; + m_viewfinderHasChanged = true; + + if (m_viewfinder) { + connect(m_viewfinder, SIGNAL(sinkChanged()), + this, SLOT(handleViewfinderChange())); + connect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + + m_busHelper->installMessageFilter(m_viewfinder); + } + + emit viewfinderChanged(); + if (oldReady != isReady()) + emit readyChanged(isReady()); + } +} + +static QList<QCameraViewfinderSettings> capsToViewfinderSettings(GstCaps *supportedCaps) +{ + QList<QCameraViewfinderSettings> settings; + + if (!supportedCaps) + return settings; + + supportedCaps = qt_gst_caps_normalize(supportedCaps); + + // Convert caps to QCameraViewfinderSettings + for (uint i = 0; i < gst_caps_get_size(supportedCaps); ++i) { + const GstStructure *structure = gst_caps_get_structure(supportedCaps, i); + + QCameraViewfinderSettings s; + s.setResolution(QGstUtils::structureResolution(structure)); + s.setPixelFormat(QGstUtils::structurePixelFormat(structure)); + s.setPixelAspectRatio(QGstUtils::structurePixelAspectRatio(structure)); + + QPair<qreal, qreal> frameRateRange = QGstUtils::structureFrameRateRange(structure); + s.setMinimumFrameRate(frameRateRange.first); + s.setMaximumFrameRate(frameRateRange.second); + + if (!s.resolution().isEmpty() + && s.pixelFormat() != QVideoFrame::Format_Invalid + && !settings.contains(s)) { + settings.append(s); + } + } + + gst_caps_unref(supportedCaps); + return settings; +} + +QList<QCameraViewfinderSettings> CameraBinSession::supportedViewfinderSettings() const +{ + if (m_status >= QCamera::LoadedStatus && m_supportedViewfinderSettings.isEmpty()) { + m_supportedViewfinderSettings = + capsToViewfinderSettings(supportedCaps(QCamera::CaptureViewfinder)); + } + + return m_supportedViewfinderSettings; +} + +QCameraViewfinderSettings CameraBinSession::viewfinderSettings() const +{ + return m_status == QCamera::ActiveStatus ? m_actualViewfinderSettings : m_viewfinderSettings; +} + +void CameraBinSession::ViewfinderProbe::probeCaps(GstCaps *caps) +{ + QGstreamerVideoProbeControl::probeCaps(caps); + + // Update actual viewfinder settings on viewfinder caps change + const GstStructure *s = gst_caps_get_structure(caps, 0); + const QPair<qreal, qreal> frameRate = QGstUtils::structureFrameRateRange(s); + session->m_actualViewfinderSettings.setResolution(QGstUtils::structureResolution(s)); + session->m_actualViewfinderSettings.setMinimumFrameRate(frameRate.first); + session->m_actualViewfinderSettings.setMaximumFrameRate(frameRate.second); + session->m_actualViewfinderSettings.setPixelFormat(QGstUtils::structurePixelFormat(s)); + session->m_actualViewfinderSettings.setPixelAspectRatio(QGstUtils::structurePixelAspectRatio(s)); +} + +void CameraBinSession::handleViewfinderChange() +{ + //the viewfinder will be reloaded + //shortly when the pipeline is started + m_viewfinderHasChanged = true; + emit viewfinderChanged(); +} + +void CameraBinSession::setStatus(QCamera::Status status) +{ + if (m_status == status) + return; + + m_status = status; + emit statusChanged(m_status); + + setStateHelper(m_pendingState); +} + +QCamera::Status CameraBinSession::status() const +{ + return m_status; +} + +QCamera::State CameraBinSession::pendingState() const +{ + return m_pendingState; +} + +void CameraBinSession::setState(QCamera::State newState) +{ + if (newState == m_pendingState) + return; + + m_pendingState = newState; + emit pendingStateChanged(m_pendingState); + +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << newState; +#endif + + setStateHelper(newState); +} + +void CameraBinSession::setStateHelper(QCamera::State state) +{ + switch (state) { + case QCamera::UnloadedState: + unload(); + break; + case QCamera::LoadedState: + if (m_status == QCamera::ActiveStatus) + stop(); + else if (m_status == QCamera::UnloadedStatus) + load(); + break; + case QCamera::ActiveState: + // If the viewfinder changed while in the loaded state, we need to reload the pipeline + if (m_status == QCamera::LoadedStatus && !m_viewfinderHasChanged) + start(); + else if (m_status == QCamera::UnloadedStatus || m_viewfinderHasChanged) + load(); + } +} + +void CameraBinSession::setError(int err, const QString &errorString) +{ + // Emit only first error + if (m_pendingState == QCamera::UnloadedState) + return; + + setState(QCamera::UnloadedState); + emit error(err, errorString); + setStatus(QCamera::UnloadedStatus); +} + +void CameraBinSession::load() +{ + if (m_status != QCamera::UnloadedStatus && !m_viewfinderHasChanged) + return; + + setStatus(QCamera::LoadingStatus); + + gst_element_set_state(m_camerabin, GST_STATE_NULL); + + if (!setupCameraBin()) { + setError(QCamera::CameraError, QStringLiteral("No camera source available")); + return; + } + + m_recorderControl->applySettings(); + +#if QT_CONFIG(gstreamer_encodingprofiles) + GstEncodingContainerProfile *profile = m_recorderControl->videoProfile(); + if (profile) { + g_object_set (G_OBJECT(m_camerabin), + "video-profile", + profile, + NULL); + gst_encoding_profile_unref(profile); + } +#endif + + gst_element_set_state(m_camerabin, GST_STATE_READY); +} + +void CameraBinSession::unload() +{ + if (m_status == QCamera::UnloadedStatus || m_status == QCamera::UnloadingStatus) + return; + + setStatus(QCamera::UnloadingStatus); + + if (m_recordingActive) + stopVideoRecording(); + + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_camerabin, GST_STATE_NULL); + + if (m_busy) + emit busyChanged(m_busy = false); + + m_supportedViewfinderSettings.clear(); + + setStatus(QCamera::UnloadedStatus); +} + +void CameraBinSession::start() +{ + if (m_status != QCamera::LoadedStatus) + return; + + setStatus(QCamera::StartingStatus); + + setAudioCaptureCaps(); + + setupCaptureResolution(); + + gst_element_set_state(m_camerabin, GST_STATE_PLAYING); +} + +void CameraBinSession::stop() +{ + if (m_status != QCamera::ActiveStatus) + return; + + setStatus(QCamera::StoppingStatus); + + if (m_recordingActive) + stopVideoRecording(); + + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_camerabin, GST_STATE_READY); +} + +bool CameraBinSession::isBusy() const +{ + return m_busy; +} + +void CameraBinSession::updateBusyStatus(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + CameraBinSession *session = reinterpret_cast<CameraBinSession *>(d); + + gboolean idle = false; + g_object_get(o, "idle", &idle, NULL); + bool busy = !idle; + + if (session->m_busy != busy) { + session->m_busy = busy; + QMetaObject::invokeMethod(session, "busyChanged", + Qt::QueuedConnection, + Q_ARG(bool, busy)); + } +} + +qint64 CameraBinSession::duration() const +{ + if (m_camerabin) { + GstElement *fileSink = gst_bin_get_by_name(GST_BIN(m_camerabin), FILESINK_BIN_NAME); + if (fileSink) { + GstFormat format = GST_FORMAT_TIME; + gint64 duration = 0; + bool ret = qt_gst_element_query_position(fileSink, format, &duration); + gst_object_unref(GST_OBJECT(fileSink)); + if (ret) + return duration / 1000000; + } + } + + return 0; +} + +bool CameraBinSession::isMuted() const +{ + return m_muted; +} + +void CameraBinSession::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + + if (m_camerabin) + g_object_set(G_OBJECT(m_camerabin), MUTE_PROPERTY, m_muted, NULL); + emit mutedChanged(m_muted); + } +} + +void CameraBinSession::setCaptureDevice(const QString &deviceName) +{ + m_captureDevice = deviceName; +} + +void CameraBinSession::setMetaData(const QMap<QByteArray, QVariant> &data) +{ + m_metaData = data; + + if (m_camerabin) + QGstUtils::setMetaData(m_camerabin, data); +} + +bool CameraBinSession::processSyncMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm && GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) { + const GstStructure *st = gst_message_get_structure(gm); + const GValue *sampleValue = 0; + if (m_captureMode == QCamera::CaptureStillImage + && gst_structure_has_name(st, "preview-image") + && gst_structure_has_field_typed(st, "sample", GST_TYPE_SAMPLE) + && (sampleValue = gst_structure_get_value(st, "sample"))) { + GstSample * const sample = gst_value_get_sample(sampleValue); + GstCaps * const previewCaps = gst_sample_get_caps(sample); + GstBuffer * const buffer = gst_sample_get_buffer(sample); + + QImage image; + GstVideoInfo previewInfo; + if (gst_video_info_from_caps(&previewInfo, previewCaps)) + image = QGstUtils::bufferToImage(buffer, previewInfo); + if (!image.isNull()) { + static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageExposed); + exposedSignal.invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_requestId)); + + static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageCaptured); + capturedSignal.invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_requestId), + Q_ARG(QImage,image)); + } + return true; + } +#if QT_CONFIG(gstreamer_photography) + if (gst_structure_has_name(st, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) + m_cameraFocusControl->handleFocusMessage(gm); +#endif + } + + return false; +} + +bool CameraBinSession::processBusMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error (gm, &err, &debug); + + QString message; + + if (err && err->message) { + message = QString::fromUtf8(err->message); + qWarning() << "CameraBin error:" << message; +#if CAMERABIN_DEBUG + qWarning() << QString::fromUtf8(debug); +#endif + } + + // Only report error messages from camerabin or video source + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_camerabin) + || GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSrc)) { + if (message.isEmpty()) + message = tr("Camera error"); + + setError(int(QMediaRecorder::ResourceError), message); + } + +#ifdef CAMERABIN_DEBUG_DUMP_BIN + _gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_camerabin), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/), + "camerabin_error"); +#endif + + + if (err) + g_error_free (err); + + if (debug) + g_free (debug); + } + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_WARNING) { + GError *err; + gchar *debug; + gst_message_parse_warning (gm, &err, &debug); + + if (err && err->message) + qWarning() << "CameraBin warning:" << QString::fromUtf8(err->message); + + if (err) + g_error_free (err); + if (debug) + g_free (debug); + } + + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_camerabin)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_DURATION: + break; + + case GST_MESSAGE_STATE_CHANGED: + { + + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + +#if CAMERABIN_DEBUG + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); +#endif + +#ifdef CAMERABIN_DEBUG_DUMP_BIN + _gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_camerabin), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /*GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/), + "camerabin"); +#endif + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + setStatus(QCamera::UnloadedStatus); + break; + case GST_STATE_READY: + if (oldState == GST_STATE_NULL) + m_supportedViewfinderSettings.clear(); + + setMetaData(m_metaData); + setStatus(QCamera::LoadedStatus); + break; + case GST_STATE_PLAYING: + setStatus(QCamera::ActiveStatus); + break; + case GST_STATE_PAUSED: + default: + break; + } + } + break; + default: + break; + } + } + } + + return false; +} + +QGstreamerVideoProbeControl *CameraBinSession::videoProbe() +{ + return &m_viewfinderProbe; +} + +QString CameraBinSession::currentContainerFormat() const +{ + if (!m_muxer) + return QString(); + + QString format; + + if (GstPad *srcPad = gst_element_get_static_pad(m_muxer, "src")) { + if (GstCaps *caps = qt_gst_pad_get_caps(srcPad)) { + gchar *capsString = gst_caps_to_string(caps); + format = QString::fromLatin1(capsString); + if (capsString) + g_free(capsString); + gst_caps_unref(caps); + } + gst_object_unref(GST_OBJECT(srcPad)); + } + + return format; +} + +void CameraBinSession::recordVideo() +{ + QString format = currentContainerFormat(); + if (format.isEmpty()) + format = m_mediaContainerControl->actualContainerFormat(); + + const QString fileName = m_sink.isLocalFile() ? m_sink.toLocalFile() : m_sink.toString(); + const QFileInfo fileInfo(fileName); + const QString extension = fileInfo.suffix().isEmpty() + ? QGstUtils::fileExtensionForMimeType(format) + : fileInfo.suffix(); + + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QMediaStorageLocation::Movies, + QLatin1String("clip_"), + extension); + + m_recordingActive = true; + m_actualSink = QUrl::fromLocalFile(actualFileName); + + g_object_set(G_OBJECT(m_camerabin), FILENAME_PROPERTY, QFile::encodeName(actualFileName).constData(), NULL); + + g_signal_emit_by_name(G_OBJECT(m_camerabin), CAPTURE_START, NULL); +} + +void CameraBinSession::stopVideoRecording() +{ + m_recordingActive = false; + g_signal_emit_by_name(G_OBJECT(m_camerabin), CAPTURE_STOP, NULL); +} + +//internal, only used by CameraBinSession::supportedFrameRates. +//recursively fills the list of framerates res from value data. +static void readValue(const GValue *value, QList< QPair<int,int> > *res, bool *continuous) +{ + if (GST_VALUE_HOLDS_FRACTION(value)) { + int num = gst_value_get_fraction_numerator(value); + int denum = gst_value_get_fraction_denominator(value); + + *res << QPair<int,int>(num, denum); + } else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) { + const GValue *rateValueMin = gst_value_get_fraction_range_min(value); + const GValue *rateValueMax = gst_value_get_fraction_range_max(value); + + if (continuous) + *continuous = true; + + readValue(rateValueMin, res, continuous); + readValue(rateValueMax, res, continuous); + } else if (GST_VALUE_HOLDS_LIST(value)) { + for (uint i=0; i<gst_value_list_get_size(value); i++) { + readValue(gst_value_list_get_value(value, i), res, continuous); + } + } +} + +static bool rateLessThan(const QPair<int,int> &r1, const QPair<int,int> &r2) +{ + return r1.first*r2.second < r2.first*r1.second; +} + +GstCaps *CameraBinSession::supportedCaps(QCamera::CaptureModes mode) const +{ + GstCaps *supportedCaps = 0; + + // When using wrappercamerabinsrc, get the supported caps directly from the video source element. + // This makes sure we only get the caps actually supported by the video source element. + if (m_videoSrc) { + GstPad *pad = gst_element_get_static_pad(m_videoSrc, "src"); + if (pad) { + supportedCaps = qt_gst_pad_get_caps(pad); + gst_object_unref(GST_OBJECT(pad)); + } + } + + // Otherwise, let the camerabin handle this. + if (!supportedCaps) { + const gchar *prop; + switch (mode) { + case QCamera::CaptureStillImage: + prop = SUPPORTED_IMAGE_CAPTURE_CAPS_PROPERTY; + break; + case QCamera::CaptureVideo: + prop = SUPPORTED_VIDEO_CAPTURE_CAPS_PROPERTY; + break; + case QCamera::CaptureViewfinder: + default: + prop = SUPPORTED_VIEWFINDER_CAPS_PROPERTY; + break; + } + + g_object_get(G_OBJECT(m_camerabin), prop, &supportedCaps, NULL); + } + + return supportedCaps; +} + +QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frameSize, bool *continuous) const +{ + QList< QPair<int,int> > res; + + GstCaps *supportedCaps = this->supportedCaps(QCamera::CaptureVideo); + + if (!supportedCaps) + return res; + + GstCaps *caps = 0; + + if (frameSize.isEmpty()) { + caps = gst_caps_copy(supportedCaps); + } else { + GstCaps *filter = QGstUtils::videoFilterCaps(); + gst_caps_set_simple( + filter, + "width", G_TYPE_INT, frameSize.width(), + "height", G_TYPE_INT, frameSize.height(), + NULL); + + caps = gst_caps_intersect(supportedCaps, filter); + gst_caps_unref(filter); + } + gst_caps_unref(supportedCaps); + + //simplify to the list of rates only: + caps = gst_caps_make_writable(caps); + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + gst_structure_set_name(structure, "video/x-raw"); + gst_caps_set_features(caps, i, NULL); + const GValue *oldRate = gst_structure_get_value(structure, "framerate"); + if (!oldRate) + continue; + + GValue rate; + memset(&rate, 0, sizeof(rate)); + g_value_init(&rate, G_VALUE_TYPE(oldRate)); + g_value_copy(oldRate, &rate); + gst_structure_remove_all_fields(structure); + gst_structure_set_value(structure, "framerate", &rate); + g_value_unset(&rate); + } + caps = gst_caps_simplify(caps); + + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + const GValue *rateValue = gst_structure_get_value(structure, "framerate"); + if (!rateValue) + continue; + + readValue(rateValue, &res, continuous); + } + + std::sort(res.begin(), res.end(), rateLessThan); + +#if CAMERABIN_DEBUG + qDebug() << "Supported rates:" << caps; + qDebug() << res; +#endif + + gst_caps_unref(caps); + + return res; +} + +//internal, only used by CameraBinSession::supportedResolutions +//recursively find the supported resolutions range. +static QPair<int,int> valueRange(const GValue *value, bool *continuous) +{ + int minValue = 0; + int maxValue = 0; + + if (g_value_type_compatible(G_VALUE_TYPE(value), G_TYPE_INT)) { + minValue = maxValue = g_value_get_int(value); + } else if (GST_VALUE_HOLDS_INT_RANGE(value)) { + minValue = gst_value_get_int_range_min(value); + maxValue = gst_value_get_int_range_max(value); + *continuous = true; + } else if (GST_VALUE_HOLDS_LIST(value)) { + for (uint i=0; i<gst_value_list_get_size(value); i++) { + QPair<int,int> res = valueRange(gst_value_list_get_value(value, i), continuous); + + if (res.first > 0 && minValue > 0) + minValue = qMin(minValue, res.first); + else //select non 0 valid value + minValue = qMax(minValue, res.first); + + maxValue = qMax(maxValue, res.second); + } + } + + return QPair<int,int>(minValue, maxValue); +} + +static bool resolutionLessThan(const QSize &r1, const QSize &r2) +{ + return qlonglong(r1.width()) * r1.height() < qlonglong(r2.width()) * r2.height(); +} + + +QList<QSize> CameraBinSession::supportedResolutions(QPair<int,int> rate, + bool *continuous, + QCamera::CaptureModes mode) const +{ + QList<QSize> res; + + if (continuous) + *continuous = false; + + GstCaps *supportedCaps = this->supportedCaps(mode); + +#if CAMERABIN_DEBUG + qDebug() << "Source caps:" << supportedCaps; +#endif + + if (!supportedCaps) + return res; + + GstCaps *caps = 0; + bool isContinuous = false; + + if (rate.first <= 0 || rate.second <= 0) { + caps = gst_caps_copy(supportedCaps); + } else { + GstCaps *filter = QGstUtils::videoFilterCaps(); + gst_caps_set_simple( + filter, + "framerate" , GST_TYPE_FRACTION , rate.first, rate.second, + NULL); + caps = gst_caps_intersect(supportedCaps, filter); + gst_caps_unref(filter); + } + gst_caps_unref(supportedCaps); + + //simplify to the list of resolutions only: + caps = gst_caps_make_writable(caps); + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + gst_structure_set_name(structure, "video/x-raw"); + gst_caps_set_features(caps, i, NULL); + const GValue *oldW = gst_structure_get_value(structure, "width"); + const GValue *oldH = gst_structure_get_value(structure, "height"); + if (!oldW || !oldH) + continue; + + GValue w; + memset(&w, 0, sizeof(GValue)); + GValue h; + memset(&h, 0, sizeof(GValue)); + g_value_init(&w, G_VALUE_TYPE(oldW)); + g_value_init(&h, G_VALUE_TYPE(oldH)); + g_value_copy(oldW, &w); + g_value_copy(oldH, &h); + gst_structure_remove_all_fields(structure); + gst_structure_set_value(structure, "width", &w); + gst_structure_set_value(structure, "height", &h); + g_value_unset(&w); + g_value_unset(&h); + } + + caps = gst_caps_simplify(caps); + + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + const GValue *wValue = gst_structure_get_value(structure, "width"); + const GValue *hValue = gst_structure_get_value(structure, "height"); + if (!wValue || !hValue) + continue; + + QPair<int,int> wRange = valueRange(wValue, &isContinuous); + QPair<int,int> hRange = valueRange(hValue, &isContinuous); + + QSize minSize(wRange.first, hRange.first); + QSize maxSize(wRange.second, hRange.second); + + if (!minSize.isEmpty()) + res << minSize; + + if (minSize != maxSize && !maxSize.isEmpty()) + res << maxSize; + } + + + std::sort(res.begin(), res.end(), resolutionLessThan); + + //if the range is continuos, populate is with the common rates + if (isContinuous && res.size() >= 2) { + //fill the ragne with common value + static const QList<QSize> commonSizes = + QList<QSize>() << QSize(128, 96) + << QSize(160,120) + << QSize(176, 144) + << QSize(320, 240) + << QSize(352, 288) + << QSize(640, 480) + << QSize(848, 480) + << QSize(854, 480) + << QSize(1024, 768) + << QSize(1280, 720) // HD 720 + << QSize(1280, 1024) + << QSize(1600, 1200) + << QSize(1920, 1080) // HD + << QSize(1920, 1200) + << QSize(2048, 1536) + << QSize(2560, 1600) + << QSize(2580, 1936); + QSize minSize = res.first(); + QSize maxSize = res.last(); + res.clear(); + + for (const QSize &candidate : commonSizes) { + int w = candidate.width(); + int h = candidate.height(); + + if (w > maxSize.width() && h > maxSize.height()) + break; + + if (w >= minSize.width() && h >= minSize.height() && + w <= maxSize.width() && h <= maxSize.height()) + res << candidate; + } + + if (res.isEmpty() || res.first() != minSize) + res.prepend(minSize); + + if (res.last() != maxSize) + res.append(maxSize); + } + +#if CAMERABIN_DEBUG + qDebug() << "Supported resolutions:" << gst_caps_to_string(caps); + qDebug() << res; +#endif + + gst_caps_unref(caps); + + if (continuous) + *continuous = isContinuous; + + return res; +} + +void CameraBinSession::elementAdded(GstBin *, GstElement *element, CameraBinSession *session) +{ + GstElementFactory *factory = gst_element_get_factory(element); + + if (GST_IS_BIN(element)) { + g_signal_connect(G_OBJECT(element), "element-added", G_CALLBACK(elementAdded), session); + g_signal_connect(G_OBJECT(element), "element-removed", G_CALLBACK(elementRemoved), session); + } else if (!factory) { + // no-op + } else if (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_AUDIO_ENCODER)) { + session->m_audioEncoder = element; + session->m_audioEncodeControl->applySettings(element); + } else if (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_VIDEO_ENCODER)) { + session->m_videoEncoder = element; + session->m_videoEncodeControl->applySettings(element); + } else if (gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_MUXER)) { + session->m_muxer = element; + } +} + +void CameraBinSession::elementRemoved(GstBin *, GstElement *element, CameraBinSession *session) +{ + if (element == session->m_audioEncoder) + session->m_audioEncoder = 0; + else if (element == session->m_videoEncoder) + session->m_videoEncoder = 0; + else if (element == session->m_muxer) + session->m_muxer = 0; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinsession_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinsession_p.h new file mode 100644 index 000000000..faa440dde --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinsession_p.h @@ -0,0 +1,291 @@ +/**************************************************************************** +** +** 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 CAMERABINCAPTURESESSION_H +#define CAMERABINCAPTURESESSION_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qmediarecordercontrol.h> + +#include <QtCore/qurl.h> +#include <QtCore/qdir.h> + +#include <gst/gst.h> +#if QT_CONFIG(gstreamer_photography) +#include <gst/interfaces/photography.h> +#endif + +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstreamervideoprobecontrol_p.h> +#include <private/qmediastoragelocation_p.h> +#include "qcamera.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerMessage; +class QGstreamerBusHelper; +class CameraBinControl; +class CameraBinAudioEncoder; +class CameraBinVideoEncoder; +class CameraBinImageEncoder; +class CameraBinRecorder; +class CameraBinContainer; +class CameraBinExposure; +class CameraBinFlash; +class CameraBinFocus; +class CameraBinImageProcessing; +class CameraBinLocks; +class CameraBinZoom; +class CameraBinCaptureDestination; +class CameraBinCaptureBufferFormat; +class QGstreamerVideoRendererInterface; + +class QGstreamerElementFactory +{ +public: + virtual GstElement *buildElement() = 0; +}; + +class CameraBinSession : public QObject, + public QGstreamerBusMessageFilter, + public QGstreamerSyncMessageFilter +{ + Q_OBJECT + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) + Q_INTERFACES(QGstreamerBusMessageFilter QGstreamerSyncMessageFilter) +public: + CameraBinSession(GstElementFactory *sourceFactory, QObject *parent); + ~CameraBinSession(); + +#if QT_CONFIG(gstreamer_photography) + GstPhotography *photography(); +#endif + GstElement *cameraBin() { return m_camerabin; } + GstElement *cameraSource() { return m_cameraSrc; } + QGstreamerBusHelper *bus() { return m_busHelper; } + + QList< QPair<int,int> > supportedFrameRates(const QSize &frameSize, bool *continuous) const; + QList<QSize> supportedResolutions(QPair<int,int> rate, bool *continuous, QCamera::CaptureModes mode) const; + + QCamera::CaptureModes captureMode() { return m_captureMode; } + void setCaptureMode(QCamera::CaptureModes mode); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl& sink); + + GstElement *buildCameraSource(); + GstElementFactory *sourceFactory() const { return m_sourceFactory; } + + CameraBinControl *cameraControl() const { return m_cameraControl; } + CameraBinAudioEncoder *audioEncodeControl() const { return m_audioEncodeControl; } + CameraBinVideoEncoder *videoEncodeControl() const { return m_videoEncodeControl; } + CameraBinImageEncoder *imageEncodeControl() const { return m_imageEncodeControl; } + +#if QT_CONFIG(gstreamer_photography) + CameraBinExposure *cameraExposureControl(); + CameraBinFocus *cameraFocusControl(); +#endif + + CameraBinImageProcessing *imageProcessingControl() const { return m_imageProcessingControl; } + + CameraBinRecorder *recorderControl() const { return m_recorderControl; } + CameraBinContainer *mediaContainerControl() const { return m_mediaContainerControl; } + + QGstreamerElementFactory *audioInput() const { return m_audioInputFactory; } + void setAudioInput(QGstreamerElementFactory *audioInput); + + QGstreamerElementFactory *videoInput() const { return m_videoInputFactory; } + void setVideoInput(QGstreamerElementFactory *videoInput); + bool isReady() const; + + QObject *viewfinder() const { return m_viewfinder; } + void setViewfinder(QObject *viewfinder); + + QList<QCameraViewfinderSettings> supportedViewfinderSettings() const; + QCameraViewfinderSettings viewfinderSettings() const; + void setViewfinderSettings(const QCameraViewfinderSettings &settings) { m_viewfinderSettings = settings; } + + void captureImage(int requestId, const QString &fileName); + + QCamera::Status status() const; + QCamera::State pendingState() const; + bool isBusy() const; + + qint64 duration() const; + + void recordVideo(); + void stopVideoRecording(); + + bool isMuted() const; + + QString device() const { return m_inputDevice; } + + bool processSyncMessage(const QGstreamerMessage &message) override; + bool processBusMessage(const QGstreamerMessage &message) override; + + QGstreamerVideoProbeControl *videoProbe(); + +signals: + void statusChanged(QCamera::Status status); + void pendingStateChanged(QCamera::State state); + void durationChanged(qint64 duration); + void error(int error, const QString &errorString); + void imageExposed(int requestId); + void imageCaptured(int requestId, const QImage &img); + void mutedChanged(bool); + void viewfinderChanged(); + void readyChanged(bool); + void busyChanged(bool); + +public slots: + void setDevice(const QString &device); + void setState(QCamera::State); + void setCaptureDevice(const QString &deviceName); + void setMetaData(const QMap<QByteArray, QVariant>&); + void setMuted(bool); + +private slots: + void handleViewfinderChange(); + void setupCaptureResolution(); + +private: + void load(); + void unload(); + void start(); + void stop(); + + void setStatus(QCamera::Status status); + void setStateHelper(QCamera::State state); + void setError(int error, const QString &errorString); + + bool setupCameraBin(); + void setAudioCaptureCaps(); + GstCaps *supportedCaps(QCamera::CaptureModes mode) const; + static void updateBusyStatus(GObject *o, GParamSpec *p, gpointer d); + + QString currentContainerFormat() const; + + static void elementAdded(GstBin *bin, GstElement *element, CameraBinSession *session); + static void elementRemoved(GstBin *bin, GstElement *element, CameraBinSession *session); + + QUrl m_sink; + QUrl m_actualSink; + bool m_recordingActive; + QString m_captureDevice; + QCamera::Status m_status; + QCamera::State m_pendingState; + QString m_inputDevice; + bool m_muted; + bool m_busy; + QMediaStorageLocation m_mediaStorageLocation; + + QCamera::CaptureModes m_captureMode; + QMap<QByteArray, QVariant> m_metaData; + + QGstreamerElementFactory *m_audioInputFactory; + QGstreamerElementFactory *m_videoInputFactory; + QObject *m_viewfinder; + QGstreamerVideoRendererInterface *m_viewfinderInterface; + mutable QList<QCameraViewfinderSettings> m_supportedViewfinderSettings; + QCameraViewfinderSettings m_viewfinderSettings; + QCameraViewfinderSettings m_actualViewfinderSettings; + + CameraBinControl *m_cameraControl; + CameraBinAudioEncoder *m_audioEncodeControl; + CameraBinVideoEncoder *m_videoEncodeControl; + CameraBinImageEncoder *m_imageEncodeControl; + CameraBinRecorder *m_recorderControl; + CameraBinContainer *m_mediaContainerControl; +#if QT_CONFIG(gstreamer_photography) + CameraBinExposure *m_cameraExposureControl; + CameraBinFocus *m_cameraFocusControl; +#endif + CameraBinImageProcessing *m_imageProcessingControl; + + QGstreamerBusHelper *m_busHelper; + GstBus* m_bus; + GstElement *m_camerabin; + GstElement *m_cameraSrc; + GstElement *m_videoSrc; + GstElement *m_viewfinderElement; + GstElementFactory *m_sourceFactory; + bool m_viewfinderHasChanged; + bool m_inputDeviceHasChanged; + bool m_usingWrapperCameraBinSrc; + + class ViewfinderProbe : public QGstreamerVideoProbeControl { + public: + ViewfinderProbe(CameraBinSession *s) + : QGstreamerVideoProbeControl(s) + , session(s) + {} + + void probeCaps(GstCaps *caps) override; + + private: + CameraBinSession * const session; + } m_viewfinderProbe; + + GstElement *m_audioSrc; + GstElement *m_audioConvert; + GstElement *m_capsFilter; + GstElement *m_fileSink; + GstElement *m_audioEncoder; + GstElement *m_videoEncoder; + GstElement *m_muxer; + +public: + QString m_imageFileName; + int m_requestId; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINCAPTURESESSION_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing.cpp new file mode 100644 index 000000000..963eb8580 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Denis Shienkov <denis.shienkov@gmail.com> +** 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 "camerabinv4limageprocessing.h" +#include "camerabinsession.h" + +#include <QDebug> + +#include <private/qcore_unix_p.h> +#include <linux/videodev2.h> + +QT_BEGIN_NAMESPACE + +CameraBinV4LImageProcessing::CameraBinV4LImageProcessing(CameraBinSession *session) + : QCameraImageProcessingControl(session) + , m_session(session) +{ +} + +CameraBinV4LImageProcessing::~CameraBinV4LImageProcessing() +{ +} + +bool CameraBinV4LImageProcessing::isParameterSupported( + ProcessingParameter parameter) const +{ + return m_parametersInfo.contains(parameter); +} + +bool CameraBinV4LImageProcessing::isParameterValueSupported( + ProcessingParameter parameter, const QVariant &value) const +{ + QMap<ProcessingParameter, SourceParameterValueInfo>::const_iterator sourceValueInfo = + m_parametersInfo.constFind(parameter); + if (sourceValueInfo == m_parametersInfo.constEnd()) + return false; + + switch (parameter) { + + case QCameraImageProcessingControl::WhiteBalancePreset: { + const QCameraImageProcessing::WhiteBalanceMode checkedValue = + value.value<QCameraImageProcessing::WhiteBalanceMode>(); + const QCameraImageProcessing::WhiteBalanceMode firstAllowedValue = + (*sourceValueInfo).minimumValue ? QCameraImageProcessing::WhiteBalanceAuto + : QCameraImageProcessing::WhiteBalanceManual; + const QCameraImageProcessing::WhiteBalanceMode secondAllowedValue = + (*sourceValueInfo).maximumValue ? QCameraImageProcessing::WhiteBalanceAuto + : QCameraImageProcessing::WhiteBalanceManual; + if (checkedValue != firstAllowedValue + && checkedValue != secondAllowedValue) { + return false; + } + } + break; + + case QCameraImageProcessingControl::ColorTemperature: { + const qint32 checkedValue = value.toInt(); + if (checkedValue < (*sourceValueInfo).minimumValue + || checkedValue > (*sourceValueInfo).maximumValue) { + return false; + } + } + break; + + case QCameraImageProcessingControl::ContrastAdjustment: // falling back + case QCameraImageProcessingControl::SaturationAdjustment: // falling back + case QCameraImageProcessingControl::BrightnessAdjustment: // falling back + case QCameraImageProcessingControl::SharpeningAdjustment: { + const qint32 sourceValue = sourceImageProcessingParameterValue( + value.toReal(), (*sourceValueInfo)); + if (sourceValue < (*sourceValueInfo).minimumValue + || sourceValue > (*sourceValueInfo).maximumValue) { + return false; + } + } + break; + + default: + return false; + } + + return true; +} + +QVariant CameraBinV4LImageProcessing::parameter( + ProcessingParameter parameter) const +{ + QMap<ProcessingParameter, SourceParameterValueInfo>::const_iterator sourceValueInfo = + m_parametersInfo.constFind(parameter); + if (sourceValueInfo == m_parametersInfo.constEnd()) { + if (!m_parametersInfo.empty()) + qWarning() << "Unable to get the unsupported parameter:" << parameter; + return QVariant(); + } + + const QString deviceName = m_session->device(); + const int fd = qt_safe_open(deviceName.toLocal8Bit().constData(), O_RDONLY); + if (fd == -1) { + qWarning() << "Unable to open the camera" << deviceName + << "for read to get the parameter value:" << qt_error_string(errno); + return QVariant(); + } + + struct v4l2_control control; + ::memset(&control, 0, sizeof(control)); + control.id = (*sourceValueInfo).cid; + + const bool ret = (::ioctl(fd, VIDIOC_G_CTRL, &control) == 0); + + qt_safe_close(fd); + + if (!ret) { + qWarning() << "Unable to get the parameter value:" << parameter << ":" << qt_error_string(errno); + return QVariant(); + } + + switch (parameter) { + + case QCameraImageProcessingControl::WhiteBalancePreset: + return QVariant::fromValue<QCameraImageProcessing::WhiteBalanceMode>( + control.value ? QCameraImageProcessing::WhiteBalanceAuto + : QCameraImageProcessing::WhiteBalanceManual); + + case QCameraImageProcessingControl::ColorTemperature: + return QVariant::fromValue<qint32>(control.value); + + case QCameraImageProcessingControl::ContrastAdjustment: // falling back + case QCameraImageProcessingControl::SaturationAdjustment: // falling back + case QCameraImageProcessingControl::BrightnessAdjustment: // falling back + case QCameraImageProcessingControl::SharpeningAdjustment: { + return scaledImageProcessingParameterValue( + control.value, (*sourceValueInfo)); + } + + default: + return QVariant(); + } +} + +void CameraBinV4LImageProcessing::setParameter( + ProcessingParameter parameter, const QVariant &value) +{ + QMap<ProcessingParameter, SourceParameterValueInfo>::const_iterator sourceValueInfo = + m_parametersInfo.constFind(parameter); + if (sourceValueInfo == m_parametersInfo.constEnd()) { + if (!m_parametersInfo.empty()) + qWarning() << "Unable to set the unsupported parameter:" << parameter; + return; + } + + const QString deviceName = m_session->device(); + const int fd = qt_safe_open(deviceName.toLocal8Bit().constData(), O_WRONLY); + if (fd == -1) { + qWarning() << "Unable to open the camera" << deviceName + << "for write to set the parameter value:" << qt_error_string(errno); + return; + } + + struct v4l2_control control; + ::memset(&control, 0, sizeof(control)); + control.id = (*sourceValueInfo).cid; + + switch (parameter) { + + case QCameraImageProcessingControl::WhiteBalancePreset: { + const QCameraImageProcessing::WhiteBalanceMode m = + value.value<QCameraImageProcessing::WhiteBalanceMode>(); + if (m != QCameraImageProcessing::WhiteBalanceAuto + && m != QCameraImageProcessing::WhiteBalanceManual) { + qt_safe_close(fd); + return; + } + + control.value = (m == QCameraImageProcessing::WhiteBalanceAuto); + } + break; + + case QCameraImageProcessingControl::ColorTemperature: + control.value = value.toInt(); + break; + + case QCameraImageProcessingControl::ContrastAdjustment: // falling back + case QCameraImageProcessingControl::SaturationAdjustment: // falling back + case QCameraImageProcessingControl::BrightnessAdjustment: // falling back + case QCameraImageProcessingControl::SharpeningAdjustment: + control.value = sourceImageProcessingParameterValue( + value.toReal(), (*sourceValueInfo)); + break; + + default: + qt_safe_close(fd); + return; + } + + if (::ioctl(fd, VIDIOC_S_CTRL, &control) != 0) + qWarning() << "Unable to set the parameter value:" << parameter << ":" << qt_error_string(errno); + + qt_safe_close(fd); +} + +void CameraBinV4LImageProcessing::updateParametersInfo( + QCamera::Status cameraStatus) +{ + if (cameraStatus == QCamera::UnloadedStatus) + m_parametersInfo.clear(); + else if (cameraStatus == QCamera::LoadedStatus) { + const QString deviceName = m_session->device(); + const int fd = qt_safe_open(deviceName.toLocal8Bit().constData(), O_RDONLY); + if (fd == -1) { + qWarning() << "Unable to open the camera" << deviceName + << "for read to query the parameter info:" << qt_error_string(errno); + return; + } + + constexpr struct SupportedParameterEntry { + quint32 cid; + QCameraImageProcessingControl::ProcessingParameter parameter; + } supportedParametersEntries[] = { + { V4L2_CID_AUTO_WHITE_BALANCE, QCameraImageProcessingControl::WhiteBalancePreset }, + { V4L2_CID_WHITE_BALANCE_TEMPERATURE, QCameraImageProcessingControl::ColorTemperature }, + { V4L2_CID_CONTRAST, QCameraImageProcessingControl::ContrastAdjustment }, + { V4L2_CID_SATURATION, QCameraImageProcessingControl::SaturationAdjustment }, + { V4L2_CID_BRIGHTNESS, QCameraImageProcessingControl::BrightnessAdjustment }, + { V4L2_CID_SHARPNESS, QCameraImageProcessingControl::SharpeningAdjustment } + }; + + for (auto supportedParametersEntrie : supportedParametersEntries) { + struct v4l2_queryctrl queryControl; + ::memset(&queryControl, 0, sizeof(queryControl)); + queryControl.id = supportedParametersEntrie.cid; + + if (::ioctl(fd, VIDIOC_QUERYCTRL, &queryControl) != 0) { + qWarning() << "Unable to query the parameter info:" << supportedParametersEntrie.parameter + << ":" << qt_error_string(errno); + continue; + } + + SourceParameterValueInfo sourceValueInfo; + sourceValueInfo.cid = queryControl.id; + sourceValueInfo.defaultValue = queryControl.default_value; + sourceValueInfo.maximumValue = queryControl.maximum; + sourceValueInfo.minimumValue = queryControl.minimum; + + m_parametersInfo.insert(supportedParametersEntrie.parameter, sourceValueInfo); + } + + qt_safe_close(fd); + } +} + +qreal CameraBinV4LImageProcessing::scaledImageProcessingParameterValue( + qint32 sourceValue, const SourceParameterValueInfo &sourceValueInfo) +{ + if (sourceValue == sourceValueInfo.defaultValue) + return 0.0f; + + if (sourceValue < sourceValueInfo.defaultValue) + return ((sourceValue - sourceValueInfo.minimumValue) + / qreal(sourceValueInfo.defaultValue - sourceValueInfo.minimumValue)) + + (-1.0f); + + return ((sourceValue - sourceValueInfo.defaultValue) + / qreal(sourceValueInfo.maximumValue - sourceValueInfo.defaultValue)); +} + +qint32 CameraBinV4LImageProcessing::sourceImageProcessingParameterValue( + qreal scaledValue, const SourceParameterValueInfo &valueRange) +{ + if (qFuzzyIsNull(scaledValue)) + return valueRange.defaultValue; + + if (scaledValue < 0.0f) + return ((scaledValue - (-1.0f)) * (valueRange.defaultValue - valueRange.minimumValue)) + + valueRange.minimumValue; + + return (scaledValue * (valueRange.maximumValue - valueRange.defaultValue)) + + valueRange.defaultValue; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing_p.h new file mode 100644 index 000000000..d1ca6f0c0 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinv4limageprocessing_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Denis Shienkov <denis.shienkov@gmail.com> +** 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 CAMERABINV4LIMAGEPROCESSINGCONTROL_H +#define CAMERABINV4LIMAGEPROCESSINGCONTROL_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 <qcameraimageprocessingcontrol.h> + +QT_BEGIN_NAMESPACE + +class CameraBinSession; + +class CameraBinV4LImageProcessing : public QCameraImageProcessingControl +{ + Q_OBJECT + +public: + CameraBinV4LImageProcessing(CameraBinSession *session); + virtual ~CameraBinV4LImageProcessing(); + + 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; + +public slots: + void updateParametersInfo(QCamera::Status cameraStatus); + +private: + struct SourceParameterValueInfo { + SourceParameterValueInfo() + : cid(0) + { + } + + qint32 defaultValue; + qint32 minimumValue; + qint32 maximumValue; + quint32 cid; // V4L control id + }; + + static qreal scaledImageProcessingParameterValue( + qint32 sourceValue, const SourceParameterValueInfo &sourceValueInfo); + static qint32 sourceImageProcessingParameterValue( + qreal scaledValue, const SourceParameterValueInfo &valueRange); +private: + CameraBinSession *m_session; + QMap<ProcessingParameter, SourceParameterValueInfo> m_parametersInfo; +}; + +QT_END_NAMESPACE + +#endif // CAMERABINV4LIMAGEPROCESSINGCONTROL_H diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder.cpp b/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder.cpp new file mode 100644 index 000000000..5bba2ddb5 --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "camerabinvideoencoder.h" +#include "camerabinsession.h" +#include "camerabincontainer.h" +#include <private/qgstutils_p.h> + +#include <QtCore/qdebug.h> + +QT_BEGIN_NAMESPACE + +CameraBinVideoEncoder::CameraBinVideoEncoder(CameraBinSession *session) + :QVideoEncoderSettingsControl(session) + , m_session(session) +#if QT_CONFIG(gstreamer_encodingprofiles) + , m_codecs(QGstCodecsInfo::VideoEncoder) +#endif +{ +} + +CameraBinVideoEncoder::~CameraBinVideoEncoder() +{ +} + +QList<QSize> CameraBinVideoEncoder::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const +{ + if (continuous) + *continuous = false; + + QPair<int,int> rate = rateAsRational(settings.frameRate()); + + //select the closest supported rational rate to settings.frameRate() + + return m_session->supportedResolutions(rate, continuous, QCamera::CaptureVideo); +} + +QList< qreal > CameraBinVideoEncoder::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const +{ + if (continuous) + *continuous = false; + + QList< qreal > res; + + const auto rates = m_session->supportedFrameRates(settings.resolution(), continuous); + for (const auto &rate : rates) { + if (rate.second > 0) + res << qreal(rate.first)/rate.second; + } + + return res; +} + +QStringList CameraBinVideoEncoder::supportedVideoCodecs() const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_codecs.supportedCodecs(); +#else + return QStringList(); +#endif +} + +QString CameraBinVideoEncoder::videoCodecDescription(const QString &codecName) const +{ +#if QT_CONFIG(gstreamer_encodingprofiles) + return m_codecs.codecDescription(codecName); +#else + Q_UNUSED(codecName); + return QString(); +#endif +} + +QVideoEncoderSettings CameraBinVideoEncoder::videoSettings() const +{ + return m_videoSettings; +} + +void CameraBinVideoEncoder::setVideoSettings(const QVideoEncoderSettings &settings) +{ + if (m_videoSettings != settings) { + m_actualVideoSettings = settings; + m_videoSettings = settings; + emit settingsChanged(); + } +} + +QVideoEncoderSettings CameraBinVideoEncoder::actualVideoSettings() const +{ + return m_actualVideoSettings; +} + +void CameraBinVideoEncoder::setActualVideoSettings(const QVideoEncoderSettings &settings) +{ + m_actualVideoSettings = settings; +} + +void CameraBinVideoEncoder::resetActualSettings() +{ + m_actualVideoSettings = m_videoSettings; +} + + +QPair<int,int> CameraBinVideoEncoder::rateAsRational(qreal frameRate) const +{ + if (frameRate > 0.001) { + //convert to rational number + QList<int> denumCandidates; + denumCandidates << 1 << 2 << 3 << 5 << 10 << 25 << 30 << 50 << 100 << 1001 << 1000; + + qreal error = 1.0; + int num = 1; + int denum = 1; + + for (int curDenum : qAsConst(denumCandidates)) { + int curNum = qRound(frameRate*curDenum); + qreal curError = qAbs(qreal(curNum)/curDenum - frameRate); + + if (curError < error) { + error = curError; + num = curNum; + denum = curDenum; + } + + if (curError < 1e-8) + break; + } + + return QPair<int,int>(num,denum); + } + + return QPair<int,int>(); +} + +#if QT_CONFIG(gstreamer_encodingprofiles) + +GstEncodingProfile *CameraBinVideoEncoder::createProfile() +{ + QString codec = m_actualVideoSettings.codec(); + GstCaps *caps = !codec.isEmpty() ? gst_caps_from_string(codec.toLatin1()) : nullptr; + + if (!caps) + return nullptr; + + QString preset = m_actualVideoSettings.encodingOption(QStringLiteral("preset")).toString(); + GstEncodingVideoProfile *profile = gst_encoding_video_profile_new( + caps, + !preset.isEmpty() ? preset.toLatin1().constData() : NULL, //preset + NULL, //restriction + 1); //presence + + gst_caps_unref(caps); + + gst_encoding_video_profile_set_pass(profile, 0); + gst_encoding_video_profile_set_variableframerate(profile, TRUE); + + return (GstEncodingProfile *)profile; +} + +#endif + +void CameraBinVideoEncoder::applySettings(GstElement *encoder) +{ + GObjectClass * const objectClass = G_OBJECT_GET_CLASS(encoder); + const char * const name = qt_gst_element_get_factory_name(encoder); + + const int bitRate = m_actualVideoSettings.bitRate(); + if (bitRate == -1) { + // Bit rate is invalid, don't evaluate the remaining conditions. + } else if (g_object_class_find_property(objectClass, "bitrate")) { + g_object_set(G_OBJECT(encoder), "bitrate", bitRate, NULL); + } else if (g_object_class_find_property(objectClass, "target-bitrate")) { + g_object_set(G_OBJECT(encoder), "target-bitrate", bitRate, NULL); + } + + if (qstrcmp(name, "theoraenc") == 0) { + static const int qualities[] = { 8, 16, 32, 45, 60 }; + g_object_set(G_OBJECT(encoder), "quality", qualities[m_actualVideoSettings.quality()], NULL); + } else if (qstrncmp(name, "avenc_", 6) == 0) { + if (g_object_class_find_property(objectClass, "pass")) { + static const int modes[] = { 0, 2, 512, 1024 }; + g_object_set(G_OBJECT(encoder), "pass", modes[m_actualVideoSettings.encodingMode()], NULL); + } + if (g_object_class_find_property(objectClass, "quantizer")) { + static const double qualities[] = { 20, 8.0, 3.0, 2.5, 2.0 }; + g_object_set(G_OBJECT(encoder), "quantizer", qualities[m_actualVideoSettings.quality()], NULL); + } + } else if (qstrncmp(name, "omx", 3) == 0) { + if (!g_object_class_find_property(objectClass, "control-rate")) { + } else switch (m_actualVideoSettings.encodingMode()) { + case QMultimedia::ConstantBitRateEncoding: + g_object_set(G_OBJECT(encoder), "control-rate", 2, NULL); + break; + case QMultimedia::AverageBitRateEncoding: + g_object_set(G_OBJECT(encoder), "control-rate", 1, NULL); + break; + default: + g_object_set(G_OBJECT(encoder), "control-rate", 0, NULL); + } + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder_p.h b/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder_p.h new file mode 100644 index 000000000..15ab1a08d --- /dev/null +++ b/src/multimedia/platform/gstreamer/camerabin/camerabinvideoencoder_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** 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 CAMERABINVIDEOENCODE_H +#define CAMERABINVIDEOENCODE_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <qvideoencodersettingscontrol.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> +#include <gst/pbutils/pbutils.h> + +#if QT_CONFIG(gstreamer_encodingprofiles) +#include <gst/pbutils/encoding-profile.h> +#include <private/qgstcodecsinfo_p.h> +#endif + +QT_BEGIN_NAMESPACE + +class CameraBinSession; + +class CameraBinVideoEncoder : public QVideoEncoderSettingsControl +{ + Q_OBJECT +public: + CameraBinVideoEncoder(CameraBinSession *session); + virtual ~CameraBinVideoEncoder(); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const override; + + QList< qreal > supportedFrameRates(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const override; + + QPair<int,int> rateAsRational(qreal) const; + + QStringList supportedVideoCodecs() const override; + QString videoCodecDescription(const QString &codecName) const override; + + QVideoEncoderSettings videoSettings() const override; + void setVideoSettings(const QVideoEncoderSettings &settings) override; + + QVideoEncoderSettings actualVideoSettings() const; + void setActualVideoSettings(const QVideoEncoderSettings&); + void resetActualSettings(); + +#if QT_CONFIG(gstreamer_encodingprofiles) + GstEncodingProfile *createProfile(); +#endif + + void applySettings(GstElement *encoder); + +Q_SIGNALS: + void settingsChanged(); + +private: + CameraBinSession *m_session; + +#if QT_CONFIG(gstreamer_encodingprofiles) + QGstCodecsInfo m_codecs; +#endif + + QVideoEncoderSettings m_actualVideoSettings; + QVideoEncoderSettings m_videoSettings; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/common/common.pri b/src/multimedia/platform/gstreamer/common/common.pri new file mode 100644 index 000000000..ee1aeeb72 --- /dev/null +++ b/src/multimedia/platform/gstreamer/common/common.pri @@ -0,0 +1,41 @@ +HEADERS += \ + $$PWD/qgstappsrc_p.h \ + $$PWD/qgstreamerbushelper_p.h \ + $$PWD/qgstreamermessage_p.h \ + $$PWD/qgstutils_p.h \ + $$PWD/qgstvideobuffer_p.h \ + $$PWD/qgstreamerbufferprobe_p.h \ + $$PWD/qgstreamervideorendererinterface_p.h \ + $$PWD/qgstreameraudioinputselector_p.h \ + $$PWD/qgstreamervideorenderer_p.h \ + $$PWD/qgstreamervideoinputdevicecontrol_p.h \ + $$PWD/qgstcodecsinfo_p.h \ + $$PWD/qgstreamervideoprobecontrol_p.h \ + $$PWD/qgstreameraudioprobecontrol_p.h \ + $$PWD/qgstreamervideowindow_p.h \ + $$PWD/qgstreamervideooverlay_p.h \ + $$PWD/qgstreamerplayersession_p.h \ + $$PWD/qgstreamerplayercontrol_p.h \ + $$PWD/qgstvideorendererplugin_p.h \ + $$PWD/qgstvideorenderersink_p.h + +SOURCES += \ + $$PWD/qgstappsrc.cpp \ + $$PWD/qgstreamerbushelper.cpp \ + $$PWD/qgstreamermessage.cpp \ + $$PWD/qgstutils.cpp \ + $$PWD/qgstvideobuffer.cpp \ + $$PWD/qgstreamerbufferprobe.cpp \ + $$PWD/qgstreamervideorendererinterface.cpp \ + $$PWD/qgstreameraudioinputselector.cpp \ + $$PWD/qgstreamervideorenderer.cpp \ + $$PWD/qgstreamervideoinputdevicecontrol.cpp \ + $$PWD/qgstcodecsinfo.cpp \ + $$PWD/qgstreamervideoprobecontrol.cpp \ + $$PWD/qgstreameraudioprobecontrol.cpp \ + $$PWD/qgstreamervideowindow.cpp \ + $$PWD/qgstreamervideooverlay.cpp \ + $$PWD/qgstreamerplayersession.cpp \ + $$PWD/qgstreamerplayercontrol.cpp \ + $$PWD/qgstvideorendererplugin.cpp \ + $$PWD/qgstvideorenderersink.cpp diff --git a/src/multimedia/platform/gstreamer/qgstappsrc.cpp b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp index 3c2df339c..3c2df339c 100644 --- a/src/multimedia/platform/gstreamer/qgstappsrc.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstappsrc.cpp diff --git a/src/multimedia/platform/gstreamer/qgstappsrc_p.h b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h index 29603eb98..29603eb98 100644 --- a/src/multimedia/platform/gstreamer/qgstappsrc_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstappsrc_p.h diff --git a/src/multimedia/platform/gstreamer/qgstcodecsinfo.cpp b/src/multimedia/platform/gstreamer/common/qgstcodecsinfo.cpp index bbf78124d..bbf78124d 100644 --- a/src/multimedia/platform/gstreamer/qgstcodecsinfo.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstcodecsinfo.cpp diff --git a/src/multimedia/platform/gstreamer/qgstcodecsinfo_p.h b/src/multimedia/platform/gstreamer/common/qgstcodecsinfo_p.h index 7f4415e69..7f4415e69 100644 --- a/src/multimedia/platform/gstreamer/qgstcodecsinfo_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstcodecsinfo_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreameraudioinputselector.cpp b/src/multimedia/platform/gstreamer/common/qgstreameraudioinputselector.cpp index e91a2af73..e91a2af73 100644 --- a/src/multimedia/platform/gstreamer/qgstreameraudioinputselector.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioinputselector.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreameraudioinputselector_p.h b/src/multimedia/platform/gstreamer/common/qgstreameraudioinputselector_p.h index 3b258ba3a..3b258ba3a 100644 --- a/src/multimedia/platform/gstreamer/qgstreameraudioinputselector_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioinputselector_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreameraudioprobecontrol.cpp b/src/multimedia/platform/gstreamer/common/qgstreameraudioprobecontrol.cpp index 8b0415bde..8b0415bde 100644 --- a/src/multimedia/platform/gstreamer/qgstreameraudioprobecontrol.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioprobecontrol.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreameraudioprobecontrol_p.h b/src/multimedia/platform/gstreamer/common/qgstreameraudioprobecontrol_p.h index b641929cd..b641929cd 100644 --- a/src/multimedia/platform/gstreamer/qgstreameraudioprobecontrol_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreameraudioprobecontrol_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamerbufferprobe.cpp b/src/multimedia/platform/gstreamer/common/qgstreamerbufferprobe.cpp index 230807466..230807466 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerbufferprobe.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamerbufferprobe.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamerbufferprobe_p.h b/src/multimedia/platform/gstreamer/common/qgstreamerbufferprobe_p.h index 5d66f7320..5d66f7320 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerbufferprobe_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamerbufferprobe_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamerbushelper.cpp b/src/multimedia/platform/gstreamer/common/qgstreamerbushelper.cpp index 2eb038dfa..2eb038dfa 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerbushelper.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamerbushelper.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamerbushelper_p.h b/src/multimedia/platform/gstreamer/common/qgstreamerbushelper_p.h index 01d3ed826..01d3ed826 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerbushelper_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamerbushelper_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamermessage.cpp b/src/multimedia/platform/gstreamer/common/qgstreamermessage.cpp index 7191565e1..7191565e1 100644 --- a/src/multimedia/platform/gstreamer/qgstreamermessage.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamermessage.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamermessage_p.h b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h index d552731d2..d552731d2 100644 --- a/src/multimedia/platform/gstreamer/qgstreamermessage_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamermessage_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamerplayercontrol.cpp b/src/multimedia/platform/gstreamer/common/qgstreamerplayercontrol.cpp index be71b81c2..be71b81c2 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerplayercontrol.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamerplayercontrol.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamerplayercontrol_p.h b/src/multimedia/platform/gstreamer/common/qgstreamerplayercontrol_p.h index dbc6e3151..dbc6e3151 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerplayercontrol_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamerplayercontrol_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamerplayersession.cpp b/src/multimedia/platform/gstreamer/common/qgstreamerplayersession.cpp index 6cccb721e..6cccb721e 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerplayersession.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamerplayersession.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamerplayersession_p.h b/src/multimedia/platform/gstreamer/common/qgstreamerplayersession_p.h index f7d09ed3d..f7d09ed3d 100644 --- a/src/multimedia/platform/gstreamer/qgstreamerplayersession_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamerplayersession_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideoinputdevicecontrol.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideoinputdevicecontrol.cpp index 088b97101..088b97101 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideoinputdevicecontrol.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideoinputdevicecontrol.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideoinputdevicecontrol_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideoinputdevicecontrol_p.h index 632b6dbb4..632b6dbb4 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideoinputdevicecontrol_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideoinputdevicecontrol_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideooverlay.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp index 06e410821..06e410821 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideooverlay.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideooverlay_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h index 883da8a4d..883da8a4d 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideooverlay_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideooverlay_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideoprobecontrol.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideoprobecontrol.cpp index 3d587eb2c..3d587eb2c 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideoprobecontrol.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideoprobecontrol.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideoprobecontrol_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideoprobecontrol_p.h index 0c4b734b2..0c4b734b2 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideoprobecontrol_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideoprobecontrol_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideorenderer.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideorenderer.cpp index c6ca935a4..c6ca935a4 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideorenderer.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideorenderer.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideorenderer_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideorenderer_p.h index 10d2c8e2e..10d2c8e2e 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideorenderer_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideorenderer_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideorendererinterface.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideorendererinterface.cpp index ae7de06f1..ae7de06f1 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideorendererinterface.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideorendererinterface.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideorendererinterface_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideorendererinterface_p.h index af163c1b5..af163c1b5 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideorendererinterface_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideorendererinterface_p.h diff --git a/src/multimedia/platform/gstreamer/qgstreamervideowindow.cpp b/src/multimedia/platform/gstreamer/common/qgstreamervideowindow.cpp index e7e3c5044..e7e3c5044 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideowindow.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideowindow.cpp diff --git a/src/multimedia/platform/gstreamer/qgstreamervideowindow_p.h b/src/multimedia/platform/gstreamer/common/qgstreamervideowindow_p.h index cae656347..cae656347 100644 --- a/src/multimedia/platform/gstreamer/qgstreamervideowindow_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstreamervideowindow_p.h diff --git a/src/multimedia/platform/gstreamer/qgstutils.cpp b/src/multimedia/platform/gstreamer/common/qgstutils.cpp index 929fbb2d3..929fbb2d3 100644 --- a/src/multimedia/platform/gstreamer/qgstutils.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstutils.cpp diff --git a/src/multimedia/platform/gstreamer/qgstutils_p.h b/src/multimedia/platform/gstreamer/common/qgstutils_p.h index 607e543be..607e543be 100644 --- a/src/multimedia/platform/gstreamer/qgstutils_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstutils_p.h diff --git a/src/multimedia/platform/gstreamer/qgstvideobuffer.cpp b/src/multimedia/platform/gstreamer/common/qgstvideobuffer.cpp index 58738ffa0..58738ffa0 100644 --- a/src/multimedia/platform/gstreamer/qgstvideobuffer.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstvideobuffer.cpp diff --git a/src/multimedia/platform/gstreamer/qgstvideobuffer_p.h b/src/multimedia/platform/gstreamer/common/qgstvideobuffer_p.h index 11a217f51..11a217f51 100644 --- a/src/multimedia/platform/gstreamer/qgstvideobuffer_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstvideobuffer_p.h diff --git a/src/multimedia/platform/gstreamer/qgstvideorendererplugin.cpp b/src/multimedia/platform/gstreamer/common/qgstvideorendererplugin.cpp index 1b63cbfa8..1b63cbfa8 100644 --- a/src/multimedia/platform/gstreamer/qgstvideorendererplugin.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstvideorendererplugin.cpp diff --git a/src/multimedia/platform/gstreamer/qgstvideorendererplugin_p.h b/src/multimedia/platform/gstreamer/common/qgstvideorendererplugin_p.h index d6f13062e..d6f13062e 100644 --- a/src/multimedia/platform/gstreamer/qgstvideorendererplugin_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstvideorendererplugin_p.h diff --git a/src/multimedia/platform/gstreamer/qgstvideorenderersink.cpp b/src/multimedia/platform/gstreamer/common/qgstvideorenderersink.cpp index 0f930cf02..0f930cf02 100644 --- a/src/multimedia/platform/gstreamer/qgstvideorenderersink.cpp +++ b/src/multimedia/platform/gstreamer/common/qgstvideorenderersink.cpp diff --git a/src/multimedia/platform/gstreamer/qgstvideorenderersink_p.h b/src/multimedia/platform/gstreamer/common/qgstvideorenderersink_p.h index 0b05bbe83..0b05bbe83 100644 --- a/src/multimedia/platform/gstreamer/qgstvideorenderersink_p.h +++ b/src/multimedia/platform/gstreamer/common/qgstvideorenderersink_p.h diff --git a/src/multimedia/platform/gstreamer/gstreamer.pri b/src/multimedia/platform/gstreamer/gstreamer.pri index 324e430f5..c393c756d 100644 --- a/src/multimedia/platform/gstreamer/gstreamer.pri +++ b/src/multimedia/platform/gstreamer/gstreamer.pri @@ -2,66 +2,24 @@ DEFINES += GLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_26 QMAKE_USE_PRIVATE += gstreamer gstreamer_app -HEADERS += \ - platform/gstreamer/qaudiointerface_gstreamer_p.h \ - platform/gstreamer/qaudiodeviceinfo_gstreamer_p.h \ - platform/gstreamer/qaudiooutput_gstreamer_p.h \ - platform/gstreamer/qaudioinput_gstreamer_p.h \ - platform/gstreamer/qaudioengine_gstreamer_p.h \ - platform/gstreamer/qgstappsrc_p.h \ - platform/gstreamer/qgstreamerbushelper_p.h \ - platform/gstreamer/qgstreamermessage_p.h \ - platform/gstreamer/qgstutils_p.h \ - platform/gstreamer/qgstvideobuffer_p.h \ - platform/gstreamer/qgstreamerbufferprobe_p.h \ - platform/gstreamer/qgstreamervideorendererinterface_p.h \ - platform/gstreamer/qgstreameraudioinputselector_p.h \ - platform/gstreamer/qgstreamervideorenderer_p.h \ - platform/gstreamer/qgstreamervideoinputdevicecontrol_p.h \ - platform/gstreamer/qgstcodecsinfo_p.h \ - platform/gstreamer/qgstreamervideoprobecontrol_p.h \ - platform/gstreamer/qgstreameraudioprobecontrol_p.h \ - platform/gstreamer/qgstreamervideowindow_p.h \ - platform/gstreamer/qgstreamervideooverlay_p.h \ - platform/gstreamer/qgstreamerplayersession_p.h \ - platform/gstreamer/qgstreamerplayercontrol_p.h \ - platform/gstreamer/qgstvideorendererplugin_p.h \ - platform/gstreamer/qgstvideorenderersink_p.h - -SOURCES += \ - platform/gstreamer/qaudiointerface_gstreamer.cpp \ - platform/gstreamer/qaudiodeviceinfo_gstreamer.cpp \ - platform/gstreamer/qaudiooutput_gstreamer.cpp \ - platform/gstreamer/qaudioinput_gstreamer.cpp \ - platform/gstreamer/qaudioengine_gstreamer.cpp \ - platform/gstreamer/qgstappsrc.cpp \ - platform/gstreamer/qgstreamerbushelper.cpp \ - platform/gstreamer/qgstreamermessage.cpp \ - platform/gstreamer/qgstutils.cpp \ - platform/gstreamer/qgstvideobuffer.cpp \ - platform/gstreamer/qgstreamerbufferprobe.cpp \ - platform/gstreamer/qgstreamervideorendererinterface.cpp \ - platform/gstreamer/qgstreameraudioinputselector.cpp \ - platform/gstreamer/qgstreamervideorenderer.cpp \ - platform/gstreamer/qgstreamervideoinputdevicecontrol.cpp \ - platform/gstreamer/qgstcodecsinfo.cpp \ - platform/gstreamer/qgstreamervideoprobecontrol.cpp \ - platform/gstreamer/qgstreameraudioprobecontrol.cpp \ - platform/gstreamer/qgstreamervideowindow.cpp \ - platform/gstreamer/qgstreamervideooverlay.cpp \ - platform/gstreamer/qgstreamerplayersession.cpp \ - platform/gstreamer/qgstreamerplayercontrol.cpp \ - platform/gstreamer/qgstvideorendererplugin.cpp \ - platform/gstreamer/qgstvideorenderersink.cpp +include(audio/audio.pri) +include(common/common.pri) +use_camerabin { + include(camerabin/camerabin.pri) + DEFINES += GST_USE_CAMERABIN +} else { + include(mediacapture/mediacapture.pri) +} +include(mediaplayer/mediaplayer.pri) qtConfig(gstreamer_gl): QMAKE_USE += gstreamer_gl android { LIBS_PRIVATE += \ -L$$(GSTREAMER_ROOT_ANDROID)/armv7/lib \ - -Wl,--whole-archive \ + -Wl,--_p.hole-archive \ -lgstapp-1.0 -lgstreamer-1.0 -lgstaudio-1.0 -lgsttag-1.0 -lgstvideo-1.0 -lgstbase-1.0 -lgstpbutils-1.0 \ -lgobject-2.0 -lgmodule-2.0 -lglib-2.0 -lffi -lintl -liconv -lorc-0.4 \ - -Wl,--no-whole-archive + -Wl,--no-_p.hole-archive } diff --git a/src/multimedia/platform/gstreamer/mediacapture/mediacapture.pri b/src/multimedia/platform/gstreamer/mediacapture/mediacapture.pri new file mode 100644 index 000000000..f87e95200 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/mediacapture.pri @@ -0,0 +1,37 @@ +INCLUDEPATH += $$PWD + +HEADERS += $$PWD/qgstreamercaptureservice_p.h \ + $$PWD/qgstreamercapturesession_p.h \ + $$PWD/qgstreameraudioencode_p.h \ + $$PWD/qgstreamervideoencode_p.h \ + $$PWD/qgstreamerrecordercontrol_p.h \ + $$PWD/qgstreamermediacontainercontrol_p.h \ + $$PWD/qgstreamercameracontrol_p.h \ + $$PWD/qgstreamercapturemetadatacontrol_p.h \ + $$PWD/qgstreamerimagecapturecontrol_p.h \ + $$PWD/qgstreamerimageencode_p.h \ + $$PWD/qgstreamercaptureserviceplugin_p.h + +SOURCES += $$PWD/qgstreamercaptureservice.cpp \ + $$PWD/qgstreamercapturesession.cpp \ + $$PWD/qgstreameraudioencode.cpp \ + $$PWD/qgstreamervideoencode.cpp \ + $$PWD/qgstreamerrecordercontrol.cpp \ + $$PWD/qgstreamermediacontainercontrol.cpp \ + $$PWD/qgstreamercameracontrol.cpp \ + $$PWD/qgstreamercapturemetadatacontrol.cpp \ + $$PWD/qgstreamerimagecapturecontrol.cpp \ + $$PWD/qgstreamerimageencode.cpp \ + $$PWD/qgstreamercaptureserviceplugin.cpp + +# Camera usage with gstreamer needs to have +CONFIG += use_gstreamer_camera + +use_gstreamer_camera:qtConfig(linux_v4l) { + DEFINES += USE_GSTREAMER_CAMERA + + HEADERS += \ + $$PWD/qgstreamerv4l2input_p.h + SOURCES += \ + $$PWD/qgstreamerv4l2input.cpp +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode.cpp new file mode 100644 index 000000000..08a3a41d0 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode.cpp @@ -0,0 +1,239 @@ +/**************************************************************************** +** +** 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 "qgstreameraudioencode_p.h" +#include "qgstreamercapturesession_p.h" +#include "qgstreamermediacontainercontrol_p.h" +#include <private/qgstutils_p.h> + +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerAudioEncode::QGstreamerAudioEncode(QObject *parent) + :QAudioEncoderSettingsControl(parent) + , m_codecs(QGstCodecsInfo::AudioEncoder) +{ +} + +QGstreamerAudioEncode::~QGstreamerAudioEncode() +{ +} + +QStringList QGstreamerAudioEncode::supportedAudioCodecs() const +{ + return m_codecs.supportedCodecs(); +} + +QString QGstreamerAudioEncode::codecDescription(const QString &codecName) const +{ + return m_codecs.codecDescription(codecName); +} + +QStringList QGstreamerAudioEncode::supportedEncodingOptions(const QString &codec) const +{ + return m_codecs.codecOptions(codec); +} + +QVariant QGstreamerAudioEncode::encodingOption( + const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void QGstreamerAudioEncode::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QList<int> QGstreamerAudioEncode::supportedSampleRates(const QAudioEncoderSettings &, bool *) const +{ + //TODO check element caps to find actual values + + return QList<int>(); +} + +QAudioEncoderSettings QGstreamerAudioEncode::audioSettings() const +{ + return m_audioSettings; +} + +void QGstreamerAudioEncode::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_audioSettings = settings; +} + + +GstElement *QGstreamerAudioEncode::createEncoder() +{ + QString codec = m_audioSettings.codec(); + GstElement *encoderElement = gst_element_factory_make(m_codecs.codecElement(codec).constData(), NULL); + if (!encoderElement) + return 0; + + GstBin * encoderBin = GST_BIN(gst_bin_new("audio-encoder-bin")); + + GstElement *sinkCapsFilter = gst_element_factory_make("capsfilter", NULL); + GstElement *srcCapsFilter = gst_element_factory_make("capsfilter", NULL); + + gst_bin_add_many(encoderBin, sinkCapsFilter, encoderElement, srcCapsFilter, NULL); + gst_element_link_many(sinkCapsFilter, encoderElement, srcCapsFilter, NULL); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(sinkCapsFilter, "sink"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(srcCapsFilter, "src"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + if (m_audioSettings.sampleRate() > 0 || m_audioSettings.channelCount() > 0) { + GstCaps *caps = gst_caps_new_empty(); + GstStructure *structure = qt_gst_structure_new_empty("audio/x-raw"); + + if (m_audioSettings.sampleRate() > 0) + gst_structure_set(structure, "rate", G_TYPE_INT, m_audioSettings.sampleRate(), NULL ); + + if (m_audioSettings.channelCount() > 0) + gst_structure_set(structure, "channels", G_TYPE_INT, m_audioSettings.channelCount(), NULL ); + + gst_caps_append_structure(caps,structure); + + g_object_set(G_OBJECT(sinkCapsFilter), "caps", caps, NULL); + + gst_caps_unref(caps); + } + + // Some encoders support several codecs. Setting a caps filter downstream with the desired + // codec (which is actually a string representation of the caps) will make sure we use the + // correct codec. + GstCaps *caps = gst_caps_from_string(codec.toUtf8().constData()); + g_object_set(G_OBJECT(srcCapsFilter), "caps", caps, NULL); + gst_caps_unref(caps); + + if (encoderElement) { + if (m_audioSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + QMultimedia::EncodingQuality qualityValue = m_audioSettings.quality(); + + if (codec == QLatin1String("audio/x-vorbis")) { + double qualityTable[] = { + 0.1, //VeryLow + 0.3, //Low + 0.5, //Normal + 0.7, //High + 1.0 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", qualityTable[qualityValue], NULL); + } else if (codec == QLatin1String("audio/mpeg")) { + g_object_set(G_OBJECT(encoderElement), "target", 0, NULL); //constant quality mode + qreal quality[] = { + 1, //VeryLow + 3, //Low + 5, //Normal + 7, //High + 9 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", quality[qualityValue], NULL); + } else if (codec == QLatin1String("audio/x-speex")) { + //0-10 range with default 8 + double qualityTable[] = { + 2, //VeryLow + 5, //Low + 8, //Normal + 9, //High + 10 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", qualityTable[qualityValue], NULL); + } else if (codec.startsWith("audio/AMR")) { + int band[] = { + 0, //VeryLow + 2, //Low + 4, //Normal + 6, //High + 7 //VeryHigh + }; + + g_object_set(G_OBJECT(encoderElement), "band-mode", band[qualityValue], NULL); + } + } else { + int bitrate = m_audioSettings.bitRate(); + if (bitrate > 0) { + if (codec == QLatin1String("audio/mpeg")) { + g_object_set(G_OBJECT(encoderElement), "target", 1, NULL); //constant bitrate mode + } + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString, QVariant> options = m_options.value(codec); + for (auto it = options.cbegin(), end = options.cend(); it != end; ++it) { + const QString &option = it.key(); + const QVariant &value = it.value(); + + switch (value.typeId()) { + case QMetaType::Int: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toInt(), NULL); + break; + case QMetaType::Bool: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toBool(), NULL); + break; + case QMetaType::Double: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toDouble(), NULL); + break; + case QMetaType::QString: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + + } + } + + return GST_ELEMENT(encoderBin); +} + + +QSet<QString> QGstreamerAudioEncode::supportedStreamTypes(const QString &codecName) const +{ + return m_codecs.supportedStreamTypes(codecName); +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode_p.h new file mode 100644 index 000000000..f77a6fab9 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreameraudioencode_p.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** 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 QGSTREAMERAUDIOENCODE_H +#define QGSTREAMERAUDIOENCODE_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.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +#include <qaudioformat.h> +#include <private/qgstcodecsinfo_p.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerCaptureSession; + +class QGstreamerAudioEncode : public QAudioEncoderSettingsControl +{ + Q_OBJECT +public: + QGstreamerAudioEncode(QObject *parent); + virtual ~QGstreamerAudioEncode(); + + QStringList supportedAudioCodecs() const override; + QString codecDescription(const QString &codecName) const override; + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings = QAudioEncoderSettings(), + bool *isContinuous = 0) const override; + QList<int> supportedChannelCounts(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + QList<int> supportedSampleSizes(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + + QAudioEncoderSettings audioSettings() const override; + void setAudioSettings(const QAudioEncoderSettings &) override; + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + +private: + QGstCodecsInfo m_codecs; + + QMap<QString, QMap<QString, QVariant> > m_options; + + QAudioEncoderSettings m_audioSettings; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol.cpp new file mode 100644 index 000000000..277880c35 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol.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 "qgstreamercameracontrol_p.h" +#include "qgstreamerimageencode_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + + +QGstreamerCameraControl::QGstreamerCameraControl(QGstreamerCaptureSession *session) + :QCameraControl(session), + m_captureMode(QCamera::CaptureStillImage), + m_session(session), + m_state(QCamera::UnloadedState), + m_status(QCamera::UnloadedStatus), + m_reloadPending(false) + +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), + this, SLOT(updateStatus())); + + connect(m_session->imageEncodeControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(viewfinderChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(readyChanged(bool)), + SLOT(reloadLater())); + + m_session->setCaptureMode(QGstreamerCaptureSession::Image); +} + +QGstreamerCameraControl::~QGstreamerCameraControl() +{ +} + +void QGstreamerCameraControl::setCaptureMode(QCamera::CaptureModes mode) +{ + if (m_captureMode == mode || !isCaptureModeSupported(mode)) + return; + + m_captureMode = mode; + + switch (mode) { + case QCamera::CaptureViewfinder: + case QCamera::CaptureStillImage: + m_session->setCaptureMode(QGstreamerCaptureSession::Image); + break; + case QCamera::CaptureVideo: + m_session->setCaptureMode(QGstreamerCaptureSession::AudioAndVideo); + break; + case QCamera::CaptureVideo | QCamera::CaptureStillImage: + m_session->setCaptureMode(QGstreamerCaptureSession::AudioAndVideoAndImage); + break; + } + + emit captureModeChanged(mode); + updateStatus(); + reloadLater(); +} + +bool QGstreamerCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + //only CaptureStillImage and CaptureVideo bits are allowed + return (mode & (QCamera::CaptureStillImage | QCamera::CaptureVideo)) == mode; +} + +void QGstreamerCameraControl::setState(QCamera::State state) +{ + if (m_state == state) + return; + + m_state = state; + switch (state) { + case QCamera::UnloadedState: + case QCamera::LoadedState: + m_session->setState(QGstreamerCaptureSession::StoppedState); + break; + case QCamera::ActiveState: + //postpone changing to Active if the session is nor ready yet + if (m_session->isReady()) { + m_session->setState(QGstreamerCaptureSession::PreviewState); + } else { +#ifdef CAMEABIN_DEBUG + qDebug() << "Camera session is not ready yet, postpone activating"; +#endif + } + break; + } + + updateStatus(); + emit stateChanged(m_state); +} + +QCamera::State QGstreamerCameraControl::state() const +{ + return m_state; +} + +void QGstreamerCameraControl::updateStatus() +{ + QCamera::Status oldStatus = m_status; + + switch (m_state) { + case QCamera::UnloadedState: + m_status = QCamera::UnloadedStatus; + break; + case QCamera::LoadedState: + m_status = QCamera::LoadedStatus; + break; + case QCamera::ActiveState: + if (m_session->state() == QGstreamerCaptureSession::StoppedState) + m_status = QCamera::StartingStatus; + else + m_status = QCamera::ActiveStatus; + break; + } + + if (oldStatus != m_status) { + //qDebug() << "Status changed:" << m_status; + emit statusChanged(m_status); + } +} + +void QGstreamerCameraControl::reloadLater() +{ + //qDebug() << "reload pipeline requested"; + if (!m_reloadPending && m_state == QCamera::ActiveState) { + m_reloadPending = true; + m_session->setState(QGstreamerCaptureSession::StoppedState); + QMetaObject::invokeMethod(this, "reloadPipeline", Qt::QueuedConnection); + } +} + +void QGstreamerCameraControl::reloadPipeline() +{ + //qDebug() << "reload pipeline"; + if (m_reloadPending) { + m_reloadPending = false; + if (m_state == QCamera::ActiveState && m_session->isReady()) { + m_session->setState(QGstreamerCaptureSession::PreviewState); + } + } +} + +bool QGstreamerCameraControl::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; + } +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol_p.h new file mode 100644 index 000000000..fb3b3312d --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercameracontrol_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** 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 QGSTREAMERCAMERACONTROL_H +#define QGSTREAMERCAMERACONTROL_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 <QHash> +#include <qcameracontrol.h> +#include "qgstreamercapturesession_p.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerCameraControl : public QCameraControl +{ + Q_OBJECT +public: + QGstreamerCameraControl( QGstreamerCaptureSession *session ); + virtual ~QGstreamerCameraControl(); + + bool isValid() const { return true; } + + QCamera::State state() const override; + void setState(QCamera::State state) override; + + QCamera::Status status() const override { return m_status; } + + QCamera::CaptureModes captureMode() const override { return m_captureMode; } + 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 + { + return QCamera::NoLock; + } + + QCamera::LockStatus lockStatus(QCamera::LockType /*lock*/) const override { return QCamera::Unlocked; } + + void searchAndLock(QCamera::LockTypes /*locks*/) override {} + void unlock(QCamera::LockTypes /*locks*/) override {} + + + QList<QCameraViewfinderSettings> supportedViewfinderSettings() const override { return {}; } + + QCameraViewfinderSettings viewfinderSettings() const override { return {}; } + void setViewfinderSettings(const QCameraViewfinderSettings &/*settings*/) override {} + +public slots: + void reloadLater(); + +private slots: + void updateStatus(); + void reloadPipeline(); + + +private: + QCamera::CaptureModes m_captureMode; + QGstreamerCaptureSession *m_session; + QCamera::State m_state; + QCamera::Status m_status; + bool m_reloadPending; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAMERACONTROL_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp new file mode 100644 index 000000000..cbabca6ee --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** 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 "qgstreamercapturemetadatacontrol_p.h" + +#include <QtMultimedia/qmediametadata.h> + +#include <gst/gst.h> +#include <gst/gstversion.h> + + +typedef QMap<QString, QByteArray> QGstreamerMetaDataKeyLookup; +Q_GLOBAL_STATIC(QGstreamerMetaDataKeyLookup, metadataKeys) + +static const QGstreamerMetaDataKeyLookup *qt_gstreamerMetaDataKeys() +{ + if (metadataKeys->isEmpty()) { + metadataKeys->insert(QMediaMetaData::Title, GST_TAG_TITLE); + metadataKeys->insert(QMediaMetaData::SubTitle, 0); + //metadataKeys->insert(QMediaMetaData::Author, 0); + metadataKeys->insert(QMediaMetaData::Comment, GST_TAG_COMMENT); + metadataKeys->insert(QMediaMetaData::Description, GST_TAG_DESCRIPTION); + //metadataKeys->insert(QMediaMetaData::Category, 0); + metadataKeys->insert(QMediaMetaData::Genre, GST_TAG_GENRE); + //metadataKeys->insert(QMediaMetaData::Year, 0); + //metadataKeys->insert(QMediaMetaData::UserRating, 0); + + metadataKeys->insert(QMediaMetaData::Language, GST_TAG_LANGUAGE_CODE); + + metadataKeys->insert(QMediaMetaData::Publisher, GST_TAG_ORGANIZATION); + metadataKeys->insert(QMediaMetaData::Copyright, GST_TAG_COPYRIGHT); + //metadataKeys->insert(QMediaMetaData::ParentalRating, 0); + //metadataKeys->insert(QMediaMetaData::RatingOrganisation, 0); + + // Media + //metadataKeys->insert(QMediaMetaData::Size, 0); + //metadataKeys->insert(QMediaMetaData::MediaType, 0); + metadataKeys->insert(QMediaMetaData::Duration, GST_TAG_DURATION); + + // Audio + metadataKeys->insert(QMediaMetaData::AudioBitRate, GST_TAG_BITRATE); + metadataKeys->insert(QMediaMetaData::AudioCodec, GST_TAG_AUDIO_CODEC); + //metadataKeys->insert(QMediaMetaData::ChannelCount, 0); + //metadataKeys->insert(QMediaMetaData::SampleRate, 0); + + // Music + metadataKeys->insert(QMediaMetaData::AlbumTitle, GST_TAG_ALBUM); + metadataKeys->insert(QMediaMetaData::AlbumArtist, GST_TAG_ARTIST); + metadataKeys->insert(QMediaMetaData::ContributingArtist, GST_TAG_PERFORMER); + metadataKeys->insert(QMediaMetaData::Composer, GST_TAG_COMPOSER); + //metadataKeys->insert(QMediaMetaData::Conductor, 0); + //metadataKeys->insert(QMediaMetaData::Lyrics, 0); + //metadataKeys->insert(QMediaMetaData::Mood, 0); + metadataKeys->insert(QMediaMetaData::TrackNumber, GST_TAG_TRACK_NUMBER); + + //metadataKeys->insert(QMediaMetaData::CoverArtUrlSmall, 0); + //metadataKeys->insert(QMediaMetaData::CoverArtUrlLarge, 0); + + // Image/Video + //metadataKeys->insert(QMediaMetaData::Resolution, 0); + //metadataKeys->insert(QMediaMetaData::PixelAspectRatio, 0); + + // Video + //metadataKeys->insert(QMediaMetaData::VideoFrameRate, 0); + //metadataKeys->insert(QMediaMetaData::VideoBitRate, 0); + metadataKeys->insert(QMediaMetaData::VideoCodec, GST_TAG_VIDEO_CODEC); + + //metadataKeys->insert(QMediaMetaData::PosterUrl, 0); + + // Movie + //metadataKeys->insert(QMediaMetaData::ChapterNumber, 0); + //metadataKeys->insert(QMediaMetaData::Director, 0); + metadataKeys->insert(QMediaMetaData::LeadPerformer, GST_TAG_PERFORMER); + //metadataKeys->insert(QMediaMetaData::Writer, 0); + + // Photos + //metadataKeys->insert(QMediaMetaData::CameraManufacturer, 0); + //metadataKeys->insert(QMediaMetaData::CameraModel, 0); + //metadataKeys->insert(QMediaMetaData::Event, 0); + //metadataKeys->insert(QMediaMetaData::Subject, 0 } + } + + return metadataKeys; +} + +QGstreamerCaptureMetaDataControl::QGstreamerCaptureMetaDataControl(QObject *parent) + :QMetaDataWriterControl(parent) +{ +} + +QVariant QGstreamerCaptureMetaDataControl::metaData(const QString &key) const +{ + QGstreamerMetaDataKeyLookup::const_iterator it = qt_gstreamerMetaDataKeys()->find(key); + if (it != qt_gstreamerMetaDataKeys()->constEnd()) + return m_values.value(it.value()); + + return QVariant(); +} + +void QGstreamerCaptureMetaDataControl::setMetaData(const QString &key, const QVariant &value) +{ + QGstreamerMetaDataKeyLookup::const_iterator it = qt_gstreamerMetaDataKeys()->find(key); + if (it != qt_gstreamerMetaDataKeys()->constEnd()) { + m_values.insert(it.value(), value); + + emit QMetaDataWriterControl::metaDataChanged(); + emit QMetaDataWriterControl::metaDataChanged(key, value); + emit metaDataChanged(m_values); + } +} + +QStringList QGstreamerCaptureMetaDataControl::availableMetaData() const +{ + QStringList res; + for (auto it = m_values.keyBegin(), end = m_values.keyEnd(); it != end; ++it) { + QString tag = qt_gstreamerMetaDataKeys()->key(*it); + if (!tag.isEmpty()) + res.append(tag); + } + + return res; +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol_p.h new file mode 100644 index 000000000..6e2106019 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturemetadatacontrol_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 QGSTREAMERCAPTUREMETADATACONTROL_H +#define QGSTREAMERCAPTUREMETADATACONTROL_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 <qmetadatawritercontrol.h> +#include <qvariant.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerCaptureMetaDataControl : public QMetaDataWriterControl +{ + Q_OBJECT +public: + QGstreamerCaptureMetaDataControl(QObject *parent); + ~QGstreamerCaptureMetaDataControl() {} + + + bool isMetaDataAvailable() const override { return true; } + bool isWritable() const override { return true; } + + QVariant metaData(const QString &key) const override; + void setMetaData(const QString &key, const QVariant &value) override; + QStringList availableMetaData() const override; + +Q_SIGNALS: + void metaDataChanged(const QMap<QByteArray, QVariant>&); + +private: + QMap<QByteArray, QVariant> m_values; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTUREMETADATACONTROL_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice.cpp new file mode 100644 index 000000000..16e5badbc --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** 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 "qgstreamercaptureservice_p.h" +#include "qgstreamercapturesession_p.h" +#include "qgstreamerrecordercontrol_p.h" +#include "qgstreamermediacontainercontrol_p.h" +#include "qgstreameraudioencode_p.h" +#include "qgstreamervideoencode_p.h" +#include "qgstreamerimageencode_p.h" +#include "qgstreamercameracontrol_p.h" +#include <private/qgstreamerbushelper_p.h> +#include "qgstreamercapturemetadatacontrol_p.h" + +#if defined(USE_GSTREAMER_CAMERA) +#include "qgstreamerv4l2input_p.h" +#endif + +#include "qgstreamerimagecapturecontrol_p.h" +#include <private/qgstreameraudioinputselector_p.h> +#include <private/qgstreamervideoinputdevicecontrol_p.h> +#include <private/qgstreameraudioprobecontrol_p.h> + +#include <private/qgstreamervideorenderer_p.h> +#include <private/qgstreamervideowindow_p.h> + +#include <qmediaserviceproviderplugin.h> + +QT_BEGIN_NAMESPACE + +QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObject *parent) + : QMediaService(parent) + , m_captureSession(0) + , m_cameraControl(0) +#if defined(USE_GSTREAMER_CAMERA) + , m_videoInput(0) +#endif + , m_metaDataControl(0) + , m_audioInputSelector(0) + , m_videoInputDevice(0) + , m_videoOutput(0) + , m_videoRenderer(0) + , m_videoWindow(0) + , m_imageCaptureControl(0) + , m_audioProbeControl(0) +{ + if (service == Q_MEDIASERVICE_AUDIOSOURCE) { + m_captureSession = new QGstreamerCaptureSession(QGstreamerCaptureSession::Audio, this); + } + +#if defined(USE_GSTREAMER_CAMERA) + if (service == Q_MEDIASERVICE_CAMERA) { + m_captureSession = new QGstreamerCaptureSession(QGstreamerCaptureSession::AudioAndVideo, this); + m_cameraControl = new QGstreamerCameraControl(m_captureSession); + m_videoInput = new QGstreamerV4L2Input(this); + m_captureSession->setVideoInput(m_videoInput); + m_videoInputDevice = new QGstreamerVideoInputDeviceControl(this); + + connect(m_videoInputDevice, SIGNAL(selectedDeviceChanged(QString)), + m_videoInput, SLOT(setDevice(QString))); + + if (m_videoInputDevice->deviceCount()) + m_videoInput->setDevice(m_videoInputDevice->deviceName(m_videoInputDevice->selectedDevice())); + + m_videoRenderer = new QGstreamerVideoRenderer(this); + + m_videoWindow = new QGstreamerVideoWindow(this); + // If the GStreamer video sink is not available, don't provide the video window control since + // it won't work anyway. + if (!m_videoWindow->videoSink()) { + delete m_videoWindow; + m_videoWindow = 0; + } + + m_imageCaptureControl = new QGstreamerImageCaptureControl(m_captureSession); + } +#endif + + m_audioInputSelector = new QGstreamerAudioInputSelector(this); + connect(m_audioInputSelector, SIGNAL(activeInputChanged(QString)), m_captureSession, SLOT(setCaptureDevice(QString))); + + if (m_captureSession && m_audioInputSelector->availableInputs().size() > 0) + m_captureSession->setCaptureDevice(m_audioInputSelector->defaultInput()); + + m_metaDataControl = new QGstreamerCaptureMetaDataControl(this); + connect(m_metaDataControl, SIGNAL(metaDataChanged(QMap<QByteArray,QVariant>)), + m_captureSession, SLOT(setMetaData(QMap<QByteArray,QVariant>))); +} + +QGstreamerCaptureService::~QGstreamerCaptureService() +{ +} + +QObject *QGstreamerCaptureService::requestControl(const char *name) +{ + if (!m_captureSession) + return 0; + + if (qstrcmp(name,QAudioInputSelectorControl_iid) == 0) + return m_audioInputSelector; + + if (qstrcmp(name,QVideoDeviceSelectorControl_iid) == 0) + return m_videoInputDevice; + + if (qstrcmp(name,QMediaRecorderControl_iid) == 0) + return m_captureSession->recorderControl(); + + if (qstrcmp(name,QAudioEncoderSettingsControl_iid) == 0) + return m_captureSession->audioEncodeControl(); + + if (qstrcmp(name,QVideoEncoderSettingsControl_iid) == 0) + return m_captureSession->videoEncodeControl(); + + if (qstrcmp(name,QImageEncoderControl_iid) == 0) + return m_captureSession->imageEncodeControl(); + + + if (qstrcmp(name,QMediaContainerControl_iid) == 0) + return m_captureSession->mediaContainerControl(); + + if (qstrcmp(name,QCameraControl_iid) == 0) + return m_cameraControl; + + if (qstrcmp(name,QMetaDataWriterControl_iid) == 0) + return m_metaDataControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + + if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) { + if (!m_audioProbeControl) { + m_audioProbeControl = new QGstreamerAudioProbeControl(this); + m_captureSession->addProbe(m_audioProbeControl); + } + m_audioProbeControl->ref.ref(); + return m_audioProbeControl; + } + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + m_videoOutput = m_videoRenderer; + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + m_videoOutput = m_videoWindow; + } + + if (m_videoOutput) { + m_captureSession->setVideoPreview(m_videoOutput); + return m_videoOutput; + } + } + + return 0; +} + +void QGstreamerCaptureService::releaseControl(QObject *control) +{ + if (!control) { + return; + } else if (control == m_videoOutput) { + m_videoOutput = 0; + m_captureSession->setVideoPreview(0); + } else if (control == m_audioProbeControl && !m_audioProbeControl->ref.deref()) { + m_captureSession->removeProbe(m_audioProbeControl); + delete m_audioProbeControl; + m_audioProbeControl = 0; + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice_p.h new file mode 100644 index 000000000..fbb8992b6 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureservice_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** 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 QGSTREAMERCAPTURESERVICE_H +#define QGSTREAMERCAPTURESERVICE_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> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE +class QAudioInputSelectorControl; +class QVideoDeviceSelectorControl; + +class QGstreamerAudioProbeControl; +class QGstreamerCaptureSession; +class QGstreamerCameraControl; +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerVideoRenderer; +class QGstreamerVideoWindow; +class QGstreamerElementFactory; +class QGstreamerCaptureMetaDataControl; +class QGstreamerImageCaptureControl; +class QGstreamerV4L2Input; + +class QGstreamerCaptureService : public QMediaService +{ + Q_OBJECT + +public: + QGstreamerCaptureService(const QString &service, QObject *parent = 0); + virtual ~QGstreamerCaptureService(); + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *) override; + +private: + void setAudioPreview(GstElement *); + + QGstreamerCaptureSession *m_captureSession; + QGstreamerCameraControl *m_cameraControl; +#if defined(USE_GSTREAMER_CAMERA) + QGstreamerV4L2Input *m_videoInput; +#endif + QGstreamerCaptureMetaDataControl *m_metaDataControl; + + QAudioInputSelectorControl *m_audioInputSelector; + QVideoDeviceSelectorControl *m_videoInputDevice; + + QObject *m_videoOutput; + + QGstreamerVideoRenderer *m_videoRenderer; + QGstreamerVideoWindow *m_videoWindow; + QGstreamerImageCaptureControl *m_imageCaptureControl; + + QGstreamerAudioProbeControl *m_audioProbeControl; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURESERVICE_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp new file mode 100644 index 000000000..5adf9c701 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp @@ -0,0 +1,126 @@ +/**************************************************************************** +** +** 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 <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include "qgstreamercaptureserviceplugin_p.h" + +//#define QT_SUPPORTEDMIMETYPES_DEBUG + +#include "qgstreamercaptureservice_p.h" +#include <private/qgstutils_p.h> + +QMediaService* QGstreamerCaptureServicePlugin::create(const QString &key) +{ + QGstUtils::initializeGst(); + + if (key == QLatin1String(Q_MEDIASERVICE_AUDIOSOURCE)) + return new QGstreamerCaptureService(key); + +#if defined(USE_GSTREAMER_CAMERA) + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA)) + return new QGstreamerCaptureService(key); +#endif + + qWarning() << "Gstreamer capture service plugin: unsupported key:" << key; + return 0; +} + +void QGstreamerCaptureServicePlugin::release(QMediaService *service) +{ + delete service; +} + +#if defined(USE_GSTREAMER_CAMERA) +QByteArray QGstreamerCaptureServicePlugin::defaultDevice(const QByteArray &service) const +{ + return service == Q_MEDIASERVICE_CAMERA + ? QGstUtils::enumerateCameras().value(0).name.toUtf8() + : QByteArray(); +} + +QList<QByteArray> QGstreamerCaptureServicePlugin::devices(const QByteArray &service) const +{ + return service == Q_MEDIASERVICE_CAMERA ? QGstUtils::cameraDevices() : QList<QByteArray>(); +} + +QString QGstreamerCaptureServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + return service == Q_MEDIASERVICE_CAMERA ? QGstUtils::cameraDescription(device) : QString(); +} + +QVariant QGstreamerCaptureServicePlugin::deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property) +{ + Q_UNUSED(service); + Q_UNUSED(device); + Q_UNUSED(property); + return QVariant(); +} + +#endif + +QMultimedia::SupportEstimate QGstreamerCaptureServicePlugin::hasSupport(const QString &mimeType, + const QStringList& codecs) const +{ + if (m_supportedMimeTypeSet.isEmpty()) + updateSupportedMimeTypes(); + + return QGstUtils::hasSupport(mimeType, codecs, m_supportedMimeTypeSet); +} + + +static bool isEncoderOrMuxer(GstElementFactory *factory) +{ + return gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_MUXER) + || gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_ENCODER); +} + +void QGstreamerCaptureServicePlugin::updateSupportedMimeTypes() const +{ + m_supportedMimeTypeSet = QGstUtils::supportedMimeTypes(isEncoderOrMuxer); +} + +QStringList QGstreamerCaptureServicePlugin::supportedMimeTypes() const +{ + return QStringList(); +} + diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin_p.h new file mode 100644 index 000000000..e0290195a --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercaptureserviceplugin_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 QGSTREAMERCAPTURESERVICEPLUGIN_H +#define QGSTREAMERCAPTURESERVICEPLUGIN_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.h> +#include <QtCore/qset.h> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + +class QGstreamerCaptureServicePlugin + : public QMediaServiceProviderPlugin +#if defined(USE_GSTREAMER_CAMERA) + , public QMediaServiceSupportedDevicesInterface +#endif + , public QMediaServiceSupportedFormatsInterface +{ + Q_OBJECT +#if defined(USE_GSTREAMER_CAMERA) + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) +#endif + Q_INTERFACES(QMediaServiceSupportedFormatsInterface) +public: + QMediaService* create(const QString &key) override; + void release(QMediaService *service) override; + +#if defined(USE_GSTREAMER_CAMERA) + QByteArray defaultDevice(const QByteArray &service) const override; + QList<QByteArray> devices(const QByteArray &service) const override; + QString deviceDescription(const QByteArray &service, const QByteArray &device) override; + QVariant deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property); +#endif + + QMultimedia::SupportEstimate hasSupport(const QString &mimeType, const QStringList &codecs) const override; + QStringList supportedMimeTypes() const override; + +private: + void updateSupportedMimeTypes() const; + + mutable QSet<QString> m_supportedMimeTypeSet; //for fast access +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURESERVICEPLUGIN_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession.cpp new file mode 100644 index 000000000..c19120a73 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession.cpp @@ -0,0 +1,1020 @@ +/**************************************************************************** +** +** 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 "qgstreamercapturesession_p.h" +#include "qgstreamerrecordercontrol_p.h" +#include "qgstreamermediacontainercontrol_p.h" +#include "qgstreameraudioencode_p.h" +#include "qgstreamervideoencode_p.h" +#include "qgstreamerimageencode_p.h" +#include <qmediarecorder.h> +#include <private/qgstreamervideorendererinterface_p.h> +#include <private/qgstreameraudioprobecontrol_p.h> +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstutils_p.h> + +#include <gst/gsttagsetter.h> +#include <gst/gstversion.h> +#include <gst/video/video.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qurl.h> +#include <QtCore/qset.h> +#include <QCoreApplication> +#include <QtCore/qmetaobject.h> +#include <QtCore/qfile.h> +#include <QtGui/qimage.h> + +QT_BEGIN_NAMESPACE + +QGstreamerCaptureSession::QGstreamerCaptureSession(QGstreamerCaptureSession::CaptureMode captureMode, QObject *parent) + :QObject(parent), + m_state(StoppedState), + m_pendingState(StoppedState), + m_waitingForEos(false), + m_pipelineMode(EmptyPipeline), + m_captureMode(captureMode), + m_audioProbe(0), + m_audioInputFactory(0), + m_audioPreviewFactory(0), + m_videoInputFactory(0), + m_viewfinder(0), + m_viewfinderInterface(0), + m_audioSrc(0), + m_audioTee(0), + m_audioPreviewQueue(0), + m_audioPreview(0), + m_audioVolume(0), + m_muted(false), + m_volume(1.0), + m_videoSrc(0), + m_videoTee(0), + m_videoPreviewQueue(0), + m_videoPreview(0), + m_imageCaptureBin(0), + m_encodeBin(0), + m_passImage(false), + m_passPrerollImage(false) +{ + m_pipeline = gst_pipeline_new("media-capture-pipeline"); + qt_gst_object_ref_sink(m_pipeline); + + m_bus = gst_element_get_bus(m_pipeline); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installMessageFilter(this); + + m_audioEncodeControl = new QGstreamerAudioEncode(this); + m_videoEncodeControl = new QGstreamerVideoEncode(this); + m_imageEncodeControl = new QGstreamerImageEncode(this); + m_recorderControl = new QGstreamerRecorderControl(this); + connect(m_recorderControl, &QGstreamerRecorderControl::error, [](int e, const QString &str) { + qWarning() << QMediaRecorder::Error(e) << ":" << str.toLatin1().constData(); + }); + m_mediaContainerControl = new QGstreamerMediaContainerControl(this); +} + +QGstreamerCaptureSession::~QGstreamerCaptureSession() +{ + setState(StoppedState); + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_pipeline)); +} + +void QGstreamerCaptureSession::setCaptureMode(CaptureMode mode) +{ + m_captureMode = mode; +} + +GstElement *QGstreamerCaptureSession::buildEncodeBin() +{ + GstElement *encodeBin = gst_bin_new("encode-bin"); + + GstElement *muxer = gst_element_factory_make( m_mediaContainerControl->formatElementName().constData(), "muxer"); + if (!muxer) { + qWarning() << "Could not create a media muxer element:" << m_mediaContainerControl->formatElementName(); + gst_object_unref(encodeBin); + return 0; + } + + // Output location was rejected in setOutputlocation() if not a local file + QUrl actualSink = QUrl::fromLocalFile(QDir::currentPath()).resolved(m_sink); + GstElement *fileSink = gst_element_factory_make("filesink", "filesink"); + g_object_set(G_OBJECT(fileSink), "location", QFile::encodeName(actualSink.toLocalFile()).constData(), NULL); + gst_bin_add_many(GST_BIN(encodeBin), muxer, fileSink, NULL); + + if (!gst_element_link(muxer, fileSink)) { + gst_object_unref(encodeBin); + return 0; + } + + if (m_captureMode & Audio) { + GstElement *audioConvert = gst_element_factory_make("audioconvert", "audioconvert"); + GstElement *audioQueue = gst_element_factory_make("queue", "audio-encode-queue"); + m_audioVolume = gst_element_factory_make("volume", "volume"); + gst_bin_add_many(GST_BIN(encodeBin), audioConvert, audioQueue, m_audioVolume, NULL); + + GstElement *audioEncoder = m_audioEncodeControl->createEncoder(); + if (!audioEncoder) { + gst_object_unref(encodeBin); + qWarning() << "Could not create an audio encoder element:" << m_audioEncodeControl->audioSettings().codec(); + return 0; + } + + gst_bin_add(GST_BIN(encodeBin), audioEncoder); + + if (!gst_element_link_many(audioConvert, audioQueue, m_audioVolume, audioEncoder, muxer, NULL)) { + m_audioVolume = 0; + gst_object_unref(encodeBin); + return 0; + } + + g_object_set(G_OBJECT(m_audioVolume), "mute", m_muted, NULL); + g_object_set(G_OBJECT(m_audioVolume), "volume", m_volume, NULL); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(audioConvert, "sink"); + gst_element_add_pad(GST_ELEMENT(encodeBin), gst_ghost_pad_new("audiosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + } + + if (m_captureMode & Video) { + GstElement *videoQueue = gst_element_factory_make("queue", "video-encode-queue"); + GstElement *colorspace = gst_element_factory_make("videoconvert", "videoconvert-encoder"); + GstElement *videoscale = gst_element_factory_make("videoscale","videoscale-encoder"); + gst_bin_add_many(GST_BIN(encodeBin), videoQueue, colorspace, videoscale, NULL); + + GstElement *videoEncoder = m_videoEncodeControl->createEncoder(); + if (!videoEncoder) { + gst_object_unref(encodeBin); + qWarning() << "Could not create a video encoder element:" << m_videoEncodeControl->videoSettings().codec(); + return 0; + } + + gst_bin_add(GST_BIN(encodeBin), videoEncoder); + + if (!gst_element_link_many(videoQueue, colorspace, videoscale, videoEncoder, muxer, NULL)) { + gst_object_unref(encodeBin); + return 0; + } + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(videoQueue, "sink"); + gst_element_add_pad(GST_ELEMENT(encodeBin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + } + + return encodeBin; +} + +GstElement *QGstreamerCaptureSession::buildAudioSrc() +{ + GstElement *audioSrc = 0; + if (m_audioInputFactory) + audioSrc = m_audioInputFactory->buildElement(); + else { + QString elementName = "alsasrc"; + QString device; + + if (m_captureDevice.startsWith("alsa:")) { + device = m_captureDevice.mid(QString("alsa:").length()); + } else if (m_captureDevice.startsWith("oss:")) { + elementName = "osssrc"; + device = m_captureDevice.mid(QString("oss:").length()); + } else if (m_captureDevice.startsWith("pulseaudio:")) { + elementName = "pulsesrc"; + } else { + elementName = "autoaudiosrc"; + } + + audioSrc = gst_element_factory_make(elementName.toLatin1().constData(), "audio_src"); + if (audioSrc && !device.isEmpty()) + g_object_set(G_OBJECT(audioSrc), "device", device.toLocal8Bit().constData(), NULL); + } + + if (!audioSrc) { + emit error(int(QMediaRecorder::ResourceError), tr("Could not create an audio source element")); + audioSrc = gst_element_factory_make("fakesrc", NULL); + } + + return audioSrc; +} + +GstElement *QGstreamerCaptureSession::buildAudioPreview() +{ + GstElement *previewElement = 0; + + if (m_audioPreviewFactory) { + previewElement = m_audioPreviewFactory->buildElement(); + } else { + + +#if 1 + previewElement = gst_element_factory_make("fakesink", "audio-preview"); +#else + GstElement *bin = gst_bin_new("audio-preview-bin"); + GstElement *visual = gst_element_factory_make("libvisual_lv_scope", "audio-preview"); + GstElement *sink = gst_element_factory_make("ximagesink", NULL); + gst_bin_add_many(GST_BIN(bin), visual, sink, NULL); + gst_element_link_many(visual,sink, NULL); + + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(visual, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("audiosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; +#endif + } + + return previewElement; +} + +GstElement *QGstreamerCaptureSession::buildVideoSrc() +{ + GstElement *videoSrc = 0; + if (m_videoInputFactory) { + videoSrc = m_videoInputFactory->buildElement(); + } else { + videoSrc = gst_element_factory_make("videotestsrc", "video_test_src"); + //videoSrc = gst_element_factory_make("v4l2src", "video_test_src"); + } + + return videoSrc; +} + +GstElement *QGstreamerCaptureSession::buildVideoPreview() +{ + GstElement *previewElement = 0; + + if (m_viewfinderInterface) { + GstElement *bin = gst_bin_new("video-preview-bin"); + GstElement *colorspace = gst_element_factory_make("videoconvert", "videoconvert-preview"); + GstElement *capsFilter = gst_element_factory_make("capsfilter", "capsfilter-video-preview"); + GstElement *preview = m_viewfinderInterface->videoSink(); + + gst_bin_add_many(GST_BIN(bin), colorspace, capsFilter, preview, NULL); + gst_element_link(colorspace,capsFilter); + gst_element_link(capsFilter,preview); + + QSize resolution; + qreal frameRate = 0; + + if (m_captureMode & Video) { + QVideoEncoderSettings videoSettings = m_videoEncodeControl->videoSettings(); + resolution = videoSettings.resolution(); + frameRate = videoSettings.frameRate(); + } else if (m_captureMode & Image) { + resolution = m_imageEncodeControl->imageSettings().resolution(); + } + + GstCaps *caps = QGstUtils::videoFilterCaps(); + + if (!resolution.isEmpty()) { + gst_caps_set_simple(caps, "width", G_TYPE_INT, resolution.width(), NULL); + gst_caps_set_simple(caps, "height", G_TYPE_INT, resolution.height(), NULL); + } + if (frameRate > 0.001) { + QPair<int,int> rate = m_videoEncodeControl->rateAsRational(); + + //qDebug() << "frame rate:" << num << denum; + + gst_caps_set_simple(caps, "framerate", GST_TYPE_FRACTION, rate.first, rate.second, NULL); + } + + //qDebug() << "set video preview caps filter:" << gst_caps_to_string(caps); + + g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL); + + gst_caps_unref(caps); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(colorspace, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; + } else { +#if 1 + previewElement = gst_element_factory_make("fakesink", "video-preview"); +#else + GstElement *bin = gst_bin_new("video-preview-bin"); + GstElement *colorspace = gst_element_factory_make("videoconvert", "videoconvert-preview"); + GstElement *preview = gst_element_factory_make("ximagesink", "video-preview"); + gst_bin_add_many(GST_BIN(bin), colorspace, preview, NULL); + gst_element_link(colorspace,preview); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(colorspace, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; +#endif + } + + return previewElement; +} + +void QGstreamerCaptureSession::probeCaps(GstCaps *caps) +{ + gst_video_info_from_caps(&m_previewInfo, caps); +} + +bool QGstreamerCaptureSession::probeBuffer(GstBuffer *buffer) +{ + if (m_passPrerollImage) { + m_passImage = false; + m_passPrerollImage = false; + + return true; + } else if (!m_passImage) { + return false; + } + + m_passImage = false; + + QImage img = QGstUtils::bufferToImage(buffer, m_previewInfo); + + if (img.isNull()) + return true; + + static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageExposed); + exposedSignal.invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_imageRequestId)); + + static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageCaptured); + capturedSignal.invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_imageRequestId), + Q_ARG(QImage,img)); + + return true; +} + +static gboolean saveImageFilter(GstElement *element, + GstBuffer *buffer, + GstPad *pad, + void *appdata) +{ + Q_UNUSED(element); + Q_UNUSED(pad); + QGstreamerCaptureSession *session = (QGstreamerCaptureSession *)appdata; + + QString fileName = session->m_imageFileName; + + if (!fileName.isEmpty()) { + QFile f(fileName); + if (f.open(QFile::WriteOnly)) { + GstMapInfo info; + if (gst_buffer_map(buffer, &info, GST_MAP_READ)) { + f.write(reinterpret_cast<const char *>(info.data), info.size); + gst_buffer_unmap(buffer, &info); + } + f.close(); + + static QMetaMethod savedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageSaved); + savedSignal.invoke(session, + Qt::QueuedConnection, + Q_ARG(int,session->m_imageRequestId), + Q_ARG(QString,fileName)); + } + } + + return TRUE; +} + +GstElement *QGstreamerCaptureSession::buildImageCapture() +{ + GstElement *bin = gst_bin_new("image-capture-bin"); + GstElement *queue = gst_element_factory_make("queue", "queue-image-capture"); + GstElement *colorspace = gst_element_factory_make("videoconvert", "videoconvert-image-capture"); + GstElement *encoder = gst_element_factory_make("jpegenc", "image-encoder"); + GstElement *sink = gst_element_factory_make("fakesink","sink-image-capture"); + + GstPad *pad = gst_element_get_static_pad(queue, "src"); + Q_ASSERT(pad); + + addProbeToPad(pad, false); + + gst_object_unref(GST_OBJECT(pad)); + + g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL); + g_signal_connect(G_OBJECT(sink), "handoff", G_CALLBACK(saveImageFilter), this); + + gst_bin_add_many(GST_BIN(bin), queue, colorspace, encoder, sink, NULL); + gst_element_link_many(queue, colorspace, encoder, sink, NULL); + + // add ghostpads + pad = gst_element_get_static_pad(queue, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("imagesink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + m_passImage = false; + m_passPrerollImage = true; + m_imageFileName = QString(); + + return bin; +} + +void QGstreamerCaptureSession::captureImage(int requestId, const QString &fileName) +{ + m_imageRequestId = requestId; + m_imageFileName = fileName; + m_passImage = true; +} + + +#define REMOVE_ELEMENT(element) { if (element) {gst_bin_remove(GST_BIN(m_pipeline), element); element = 0;} } +#define UNREF_ELEMENT(element) { if (element) { gst_object_unref(GST_OBJECT(element)); element = 0; } } + +bool QGstreamerCaptureSession::rebuildGraph(QGstreamerCaptureSession::PipelineMode newMode) +{ + removeAudioBufferProbe(); + REMOVE_ELEMENT(m_audioSrc); + REMOVE_ELEMENT(m_audioPreview); + REMOVE_ELEMENT(m_audioPreviewQueue); + REMOVE_ELEMENT(m_audioTee); + REMOVE_ELEMENT(m_videoSrc); + REMOVE_ELEMENT(m_videoPreview); + REMOVE_ELEMENT(m_videoPreviewQueue); + REMOVE_ELEMENT(m_videoTee); + REMOVE_ELEMENT(m_encodeBin); + REMOVE_ELEMENT(m_imageCaptureBin); + m_audioVolume = 0; + + bool ok = true; + + switch (newMode) { + case EmptyPipeline: + break; + case PreviewPipeline: + if (m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + m_audioPreview = buildAudioPreview(); + + ok &= m_audioSrc && m_audioPreview; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_audioSrc, m_audioPreview, NULL); + ok &= gst_element_link(m_audioSrc, m_audioPreview); + } else { + UNREF_ELEMENT(m_audioSrc); + UNREF_ELEMENT(m_audioPreview); + } + } + if (m_captureMode & Video || m_captureMode & Image) { + m_videoSrc = buildVideoSrc(); + m_videoTee = gst_element_factory_make("tee", "video-preview-tee"); + m_videoPreviewQueue = gst_element_factory_make("queue", "video-preview-queue"); + m_videoPreview = buildVideoPreview(); + m_imageCaptureBin = buildImageCapture(); + + ok &= m_videoSrc && m_videoTee && m_videoPreviewQueue && m_videoPreview && m_imageCaptureBin; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_videoSrc, m_videoTee, + m_videoPreviewQueue, m_videoPreview, + m_imageCaptureBin, NULL); + + ok &= gst_element_link(m_videoSrc, m_videoTee); + ok &= gst_element_link(m_videoTee, m_videoPreviewQueue); + ok &= gst_element_link(m_videoPreviewQueue, m_videoPreview); + ok &= gst_element_link(m_videoTee, m_imageCaptureBin); + } else { + UNREF_ELEMENT(m_videoSrc); + UNREF_ELEMENT(m_videoTee); + UNREF_ELEMENT(m_videoPreviewQueue); + UNREF_ELEMENT(m_videoPreview); + UNREF_ELEMENT(m_imageCaptureBin); + } + } + break; + case RecordingPipeline: + m_encodeBin = buildEncodeBin(); + gst_bin_add(GST_BIN(m_pipeline), m_encodeBin); + + if (m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + ok &= m_audioSrc != 0; + + gst_bin_add(GST_BIN(m_pipeline), m_audioSrc); + ok &= gst_element_link(m_audioSrc, m_encodeBin); + } + + if (m_captureMode & Video) { + m_videoSrc = buildVideoSrc(); + ok &= m_videoSrc != 0; + + gst_bin_add(GST_BIN(m_pipeline), m_videoSrc); + ok &= gst_element_link(m_videoSrc, m_encodeBin); + } + + if (!m_metaData.isEmpty()) + setMetaData(m_metaData); + + break; + case PreviewAndRecordingPipeline: + m_encodeBin = buildEncodeBin(); + if (m_encodeBin) + gst_bin_add(GST_BIN(m_pipeline), m_encodeBin); + + ok &= m_encodeBin != 0; + + if (ok && m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + m_audioPreview = buildAudioPreview(); + m_audioTee = gst_element_factory_make("tee", NULL); + m_audioPreviewQueue = gst_element_factory_make("queue", NULL); + + ok &= m_audioSrc && m_audioPreview && m_audioTee && m_audioPreviewQueue; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_audioSrc, m_audioTee, + m_audioPreviewQueue, m_audioPreview, NULL); + ok &= gst_element_link(m_audioSrc, m_audioTee); + ok &= gst_element_link(m_audioTee, m_audioPreviewQueue); + ok &= gst_element_link(m_audioPreviewQueue, m_audioPreview); + ok &= gst_element_link(m_audioTee, m_encodeBin); + } else { + UNREF_ELEMENT(m_audioSrc); + UNREF_ELEMENT(m_audioPreview); + UNREF_ELEMENT(m_audioTee); + UNREF_ELEMENT(m_audioPreviewQueue); + } + } + + if (ok && (m_captureMode & Video || m_captureMode & Image)) { + m_videoSrc = buildVideoSrc(); + m_videoPreview = buildVideoPreview(); + m_videoTee = gst_element_factory_make("tee", NULL); + m_videoPreviewQueue = gst_element_factory_make("queue", NULL); + + ok &= m_videoSrc && m_videoPreview && m_videoTee && m_videoPreviewQueue; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_videoSrc, m_videoTee, + m_videoPreviewQueue, m_videoPreview, NULL); + ok &= gst_element_link(m_videoSrc, m_videoTee); + ok &= gst_element_link(m_videoTee, m_videoPreviewQueue); + ok &= gst_element_link(m_videoPreviewQueue, m_videoPreview); + } else { + UNREF_ELEMENT(m_videoSrc); + UNREF_ELEMENT(m_videoTee); + UNREF_ELEMENT(m_videoPreviewQueue); + UNREF_ELEMENT(m_videoPreview); + } + + if (ok && (m_captureMode & Video)) + ok &= gst_element_link(m_videoTee, m_encodeBin); + } + + if (!m_metaData.isEmpty()) + setMetaData(m_metaData); + + + break; + } + + if (!ok) { + emit error(int(QMediaRecorder::FormatError),tr("Failed to build media capture pipeline.")); + } + + dumpGraph( QString("rebuild_graph_%1_%2").arg(m_pipelineMode).arg(newMode) ); +#ifdef QT_GST_CAPTURE_DEBUG + if (m_encodeBin) { + QString fileName = QString("rebuild_graph_encode_%1_%2").arg(m_pipelineMode).arg(newMode); + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(m_encodeBin), GST_DEBUG_GRAPH_SHOW_ALL, fileName.toLatin1()); + } +#endif + + if (ok) { + addAudioBufferProbe(); + m_pipelineMode = newMode; + } else { + m_pipelineMode = EmptyPipeline; + + REMOVE_ELEMENT(m_audioSrc); + REMOVE_ELEMENT(m_audioPreview); + REMOVE_ELEMENT(m_audioPreviewQueue); + REMOVE_ELEMENT(m_audioTee); + REMOVE_ELEMENT(m_videoSrc); + REMOVE_ELEMENT(m_videoPreview); + REMOVE_ELEMENT(m_videoPreviewQueue); + REMOVE_ELEMENT(m_videoTee); + REMOVE_ELEMENT(m_encodeBin); + } + + return ok; +} + +void QGstreamerCaptureSession::dumpGraph(const QString &fileName) +{ +#ifdef QT_GST_CAPTURE_DEBUG + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(m_pipeline), + GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES), + fileName.toLatin1()); +#else + Q_UNUSED(fileName); +#endif +} + +QUrl QGstreamerCaptureSession::outputLocation() const +{ + return m_sink; +} + +bool QGstreamerCaptureSession::setOutputLocation(const QUrl& sink) +{ + if (!sink.isRelative() && !sink.isLocalFile()) { + qWarning("Output location must be a local file"); + return false; + } + + m_sink = sink; + return true; +} + +void QGstreamerCaptureSession::setAudioInput(QGstreamerElementFactory *audioInput) +{ + m_audioInputFactory = audioInput; +} + +void QGstreamerCaptureSession::setAudioPreview(QGstreamerElementFactory *audioPreview) +{ + m_audioPreviewFactory = audioPreview; +} + +void QGstreamerCaptureSession::setVideoInput(QGstreamerVideoInput *videoInput) +{ + m_videoInputFactory = videoInput; +} + +void QGstreamerCaptureSession::setVideoPreview(QObject *viewfinder) +{ + m_viewfinderInterface = qobject_cast<QGstreamerVideoRendererInterface*>(viewfinder); + if (!m_viewfinderInterface) + viewfinder = 0; + + if (m_viewfinder != viewfinder) { + bool oldReady = isReady(); + + if (m_viewfinder) { + disconnect(m_viewfinder, SIGNAL(sinkChanged()), + this, SIGNAL(viewfinderChanged())); + disconnect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + + m_busHelper->removeMessageFilter(m_viewfinder); + } + + m_viewfinder = viewfinder; + //m_viewfinderHasChanged = true; + + if (m_viewfinder) { + connect(m_viewfinder, SIGNAL(sinkChanged()), + this, SIGNAL(viewfinderChanged())); + connect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + + m_busHelper->installMessageFilter(m_viewfinder); + } + + emit viewfinderChanged(); + if (oldReady != isReady()) + emit readyChanged(isReady()); + } +} + +bool QGstreamerCaptureSession::isReady() const +{ + //it's possible to use QCamera without any viewfinder attached + return !m_viewfinderInterface || m_viewfinderInterface->isReady(); +} + +QGstreamerCaptureSession::State QGstreamerCaptureSession::state() const +{ + return m_state; +} + +QGstreamerCaptureSession::State QGstreamerCaptureSession::pendingState() const +{ + return m_pendingState; +} + +void QGstreamerCaptureSession::setState(QGstreamerCaptureSession::State newState) +{ + if (newState == m_pendingState && !m_waitingForEos) + return; + + m_pendingState = newState; + + PipelineMode newMode = EmptyPipeline; + + switch (newState) { + case PausedState: + case RecordingState: + newMode = PreviewAndRecordingPipeline; + break; + case PreviewState: + newMode = PreviewPipeline; + break; + case StoppedState: + newMode = EmptyPipeline; + break; + } + + if (newMode != m_pipelineMode) { + if (m_pipelineMode == PreviewAndRecordingPipeline) { + if (!m_waitingForEos) { + m_waitingForEos = true; + //qDebug() << "Waiting for EOS"; + // Unless gstreamer is in GST_STATE_PLAYING our EOS message will not be received. + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + //with live sources it's necessary to send EOS even to pipeline + //before going to STOPPED state + gst_element_send_event(m_pipeline, gst_event_new_eos()); + + return; + } else { + m_waitingForEos = false; + //qDebug() << "EOS received"; + } + } + + //select suitable default codecs/containers, if necessary + m_recorderControl->applySettings(); + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + + if (!rebuildGraph(newMode)) { + m_pendingState = StoppedState; + m_state = StoppedState; + emit stateChanged(StoppedState); + + return; + } + } + + switch (newState) { + case PausedState: + gst_element_set_state(m_pipeline, GST_STATE_PAUSED); + break; + case RecordingState: + case PreviewState: + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + break; + case StoppedState: + gst_element_set_state(m_pipeline, GST_STATE_NULL); + } + + //we have to do it here, since gstreamer will not emit bus messages any more + if (newState == StoppedState) { + m_state = StoppedState; + emit stateChanged(StoppedState); + } +} + + +qint64 QGstreamerCaptureSession::duration() const +{ + gint64 duration = 0; + if (m_encodeBin && qt_gst_element_query_position(m_encodeBin, GST_FORMAT_TIME, &duration)) + return duration / 1000000; + else + return 0; +} + +void QGstreamerCaptureSession::setCaptureDevice(const QString &deviceName) +{ + m_captureDevice = deviceName; +} + +void QGstreamerCaptureSession::setMetaData(const QMap<QByteArray, QVariant> &data) +{ + //qDebug() << "QGstreamerCaptureSession::setMetaData" << data; + m_metaData = data; + + if (m_encodeBin) + QGstUtils::setMetaData(GST_BIN(m_encodeBin), data); +} + +bool QGstreamerCaptureSession::processBusMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error (gm, &err, &debug); + emit error(int(QMediaRecorder::ResourceError),QString::fromUtf8(err->message)); + g_error_free (err); + g_free (debug); + } + + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_pipeline)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_DURATION: + break; + + case GST_MESSAGE_EOS: + if (m_waitingForEos) + setState(m_pendingState); + break; + + case GST_MESSAGE_STATE_CHANGED: + { + + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + /* + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); + + #define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + + qDebug() << "Current session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_state); + qDebug() << "Pending session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_pendingState); + */ + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + case GST_STATE_READY: + if (m_state != StoppedState && m_pendingState == StoppedState) { + emit stateChanged(m_state = StoppedState); + dumpGraph("stopped"); + } + break; + case GST_STATE_PAUSED: + if (m_state != PausedState && m_pendingState == PausedState) + emit stateChanged(m_state = PausedState); + dumpGraph("paused"); + + if (m_pipelineMode == RecordingPipeline && !m_metaData.isEmpty()) + setMetaData(m_metaData); + break; + case GST_STATE_PLAYING: + { + if ((m_pendingState == PreviewState || m_pendingState == RecordingState) && + m_state != m_pendingState) + { + m_state = m_pendingState; + emit stateChanged(m_state); + } + + if (m_pipelineMode == PreviewPipeline) + dumpGraph("preview"); + else + dumpGraph("recording"); + } + break; + } + } + break; + default: + break; + } + //qDebug() << "New session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_state); + } + } + return false; +} + +void QGstreamerCaptureSession::setMuted(bool muted) +{ + if (bool(m_muted) != muted) { + m_muted = muted; + if (m_audioVolume) + g_object_set(G_OBJECT(m_audioVolume), "mute", m_muted, NULL); + + emit mutedChanged(muted); + } +} + +void QGstreamerCaptureSession::setVolume(qreal volume) +{ + if (!qFuzzyCompare(double(volume), m_volume)) { + m_volume = volume; + if (m_audioVolume) + g_object_set(G_OBJECT(m_audioVolume), "volume", m_volume, NULL); + + emit volumeChanged(volume); + } +} + +void QGstreamerCaptureSession::addProbe(QGstreamerAudioProbeControl* probe) +{ + Q_ASSERT(!m_audioProbe); + m_audioProbe = probe; + addAudioBufferProbe(); +} + +void QGstreamerCaptureSession::removeProbe(QGstreamerAudioProbeControl* probe) +{ + Q_ASSERT(m_audioProbe == probe); + removeAudioBufferProbe(); + m_audioProbe = 0; +} + +GstPad *QGstreamerCaptureSession::getAudioProbePad() +{ + // first see if preview element is available + if (m_audioPreview) { + GstPad *pad = gst_element_get_static_pad(m_audioPreview, "sink"); + if (pad) + return pad; + } + + // preview element is not available, + // try to use sink pin of audio encoder. + if (m_encodeBin) { + GstElement *audioEncoder = gst_bin_get_by_name(GST_BIN(m_encodeBin), "audio-encoder-bin"); + if (audioEncoder) { + GstPad *pad = gst_element_get_static_pad(audioEncoder, "sink"); + gst_object_unref(audioEncoder); + if (pad) + return pad; + } + } + + return 0; +} + +void QGstreamerCaptureSession::removeAudioBufferProbe() +{ + if (!m_audioProbe) + return; + + GstPad *pad = getAudioProbePad(); + if (pad) { + m_audioProbe->removeProbeFromPad(pad); + gst_object_unref(GST_OBJECT(pad)); + } +} + +void QGstreamerCaptureSession::addAudioBufferProbe() +{ + if (!m_audioProbe) + return; + + GstPad *pad = getAudioProbePad(); + if (pad) { + m_audioProbe->addProbeToPad(pad); + gst_object_unref(GST_OBJECT(pad)); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession_p.h new file mode 100644 index 000000000..3c3e0c82f --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamercapturesession_p.h @@ -0,0 +1,253 @@ +/**************************************************************************** +** +** 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 QGSTREAMERCAPTURESESSION_H +#define QGSTREAMERCAPTURESESSION_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> +#include <qmediarecorder.h> + +#include <QtCore/qmutex.h> +#include <QtCore/qurl.h> + +#include <gst/gst.h> +#include <gst/video/video.h> + +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstreamerbufferprobe_p.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerAudioEncode; +class QGstreamerVideoEncode; +class QGstreamerImageEncode; +class QGstreamerRecorderControl; +class QGstreamerMediaContainerControl; +class QGstreamerVideoRendererInterface; +class QGstreamerAudioProbeControl; + +class QGstreamerElementFactory +{ +public: + virtual GstElement *buildElement() = 0; + virtual void prepareWinId() {} +}; + +class QGstreamerVideoInput : public QGstreamerElementFactory +{ +public: + virtual QList<qreal> supportedFrameRates(const QSize &frameSize = QSize()) const = 0; + virtual QList<QSize> supportedResolutions(qreal frameRate = -1) const = 0; +}; + +class QGstreamerCaptureSession + : public QObject + , public QGstreamerBusMessageFilter + , private QGstreamerBufferProbe +{ + Q_OBJECT + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) + Q_ENUMS(State) + Q_ENUMS(CaptureMode) + Q_INTERFACES(QGstreamerBusMessageFilter) +public: + enum CaptureMode { Audio = 1, + Video = 2, + Image = 4, + AudioAndVideo = Audio | Video, + AudioAndVideoAndImage = Audio | Video | Image + }; + enum State { StoppedState, PreviewState, PausedState, RecordingState }; + + QGstreamerCaptureSession(CaptureMode captureMode, QObject *parent); + ~QGstreamerCaptureSession(); + + QGstreamerBusHelper *bus() { return m_busHelper; } + + CaptureMode captureMode() const { return m_captureMode; } + void setCaptureMode(CaptureMode); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl& sink); + + QGstreamerAudioEncode *audioEncodeControl() const { return m_audioEncodeControl; } + QGstreamerVideoEncode *videoEncodeControl() const { return m_videoEncodeControl; } + QGstreamerImageEncode *imageEncodeControl() const { return m_imageEncodeControl; } + + QGstreamerRecorderControl *recorderControl() const { return m_recorderControl; } + QGstreamerMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } + + QGstreamerElementFactory *audioInput() const { return m_audioInputFactory; } + void setAudioInput(QGstreamerElementFactory *audioInput); + + QGstreamerElementFactory *audioPreview() const { return m_audioPreviewFactory; } + void setAudioPreview(QGstreamerElementFactory *audioPreview); + + QGstreamerVideoInput *videoInput() const { return m_videoInputFactory; } + void setVideoInput(QGstreamerVideoInput *videoInput); + + QObject *videoPreview() const { return m_viewfinder; } + void setVideoPreview(QObject *viewfinder); + + void captureImage(int requestId, const QString &fileName); + + State state() const; + State pendingState() const; + + qint64 duration() const; + bool isMuted() const { return m_muted; } + qreal volume() const { return m_volume; } + + bool isReady() const; + + bool processBusMessage(const QGstreamerMessage &message) override; + + void addProbe(QGstreamerAudioProbeControl* probe); + void removeProbe(QGstreamerAudioProbeControl* probe); + +signals: + void stateChanged(QGstreamerCaptureSession::State state); + void durationChanged(qint64 duration); + void error(int error, const QString &errorString); + void imageExposed(int requestId); + void imageCaptured(int requestId, const QImage &img); + void imageSaved(int requestId, const QString &path); + void mutedChanged(bool); + void volumeChanged(qreal); + void readyChanged(bool); + void viewfinderChanged(); + +public slots: + void setState(QGstreamerCaptureSession::State); + void setCaptureDevice(const QString &deviceName); + + void dumpGraph(const QString &fileName); + + void setMetaData(const QMap<QByteArray, QVariant>&); + void setMuted(bool); + void setVolume(qreal volume); + +private: + void probeCaps(GstCaps *caps) override; + bool probeBuffer(GstBuffer *buffer) override; + + enum PipelineMode { EmptyPipeline, PreviewPipeline, RecordingPipeline, PreviewAndRecordingPipeline }; + + GstElement *buildEncodeBin(); + GstElement *buildAudioSrc(); + GstElement *buildAudioPreview(); + GstElement *buildVideoSrc(); + GstElement *buildVideoPreview(); + GstElement *buildImageCapture(); + + bool rebuildGraph(QGstreamerCaptureSession::PipelineMode newMode); + + GstPad *getAudioProbePad(); + void removeAudioBufferProbe(); + void addAudioBufferProbe(); + + QUrl m_sink; + QString m_captureDevice; + State m_state; + State m_pendingState; + bool m_waitingForEos; + PipelineMode m_pipelineMode; + QGstreamerCaptureSession::CaptureMode m_captureMode; + QMap<QByteArray, QVariant> m_metaData; + + QGstreamerAudioProbeControl *m_audioProbe; + + QGstreamerElementFactory *m_audioInputFactory; + QGstreamerElementFactory *m_audioPreviewFactory; + QGstreamerVideoInput *m_videoInputFactory; + QObject *m_viewfinder; + QGstreamerVideoRendererInterface *m_viewfinderInterface; + + QGstreamerAudioEncode *m_audioEncodeControl; + QGstreamerVideoEncode *m_videoEncodeControl; + QGstreamerImageEncode *m_imageEncodeControl; + QGstreamerRecorderControl *m_recorderControl; + QGstreamerMediaContainerControl *m_mediaContainerControl; + + QGstreamerBusHelper *m_busHelper; + GstBus* m_bus; + GstElement *m_pipeline; + + GstElement *m_audioSrc; + GstElement *m_audioTee; + GstElement *m_audioPreviewQueue; + GstElement *m_audioPreview; + GstElement *m_audioVolume; + gboolean m_muted; + double m_volume; + + GstElement *m_videoSrc; + GstElement *m_videoTee; + GstElement *m_videoPreviewQueue; + GstElement *m_videoPreview; + + GstElement *m_imageCaptureBin; + + GstElement *m_encodeBin; + + GstVideoInfo m_previewInfo; + +public: + bool m_passImage; + bool m_passPrerollImage; + QString m_imageFileName; + int m_imageRequestId; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURESESSION_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp new file mode 100644 index 000000000..f89c30110 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp @@ -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$ +** +****************************************************************************/ + +#include "qgstreamerimagecapturecontrol_p.h" +#include <QtCore/QDebug> +#include <QtCore/QDir> + +QGstreamerImageCaptureControl::QGstreamerImageCaptureControl(QGstreamerCaptureSession *session) + :QCameraImageCaptureControl(session), m_session(session), m_ready(false), m_lastId(0) +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), SLOT(updateState())); + 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(imageSaved(int,QString)), this, SIGNAL(imageSaved(int,QString))); +} + +QGstreamerImageCaptureControl::~QGstreamerImageCaptureControl() +{ +} + +bool QGstreamerImageCaptureControl::isReadyForCapture() const +{ + return m_ready; +} + +int QGstreamerImageCaptureControl::capture(const QString &fileName) +{ + m_lastId++; + + //it's allowed to request image capture while camera is starting + if (m_session->pendingState() == QGstreamerCaptureSession::StoppedState || + !(m_session->captureMode() & QGstreamerCaptureSession::Image)) { + //emit error in the next event loop, + //so application can associate it with returned request id. + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(int, m_lastId), + Q_ARG(int, QCameraImageCapture::NotReadyError), + Q_ARG(QString,tr("Not ready to capture"))); + + return m_lastId; + } + + QString path = fileName; + if (path.isEmpty()) { + int lastImage = 0; + QDir outputDir = QDir::currentPath(); + const auto list = outputDir.entryList(QStringList() << "img_*.jpg"); + for (const QString &fileName : list) { + int imgNumber = QStringView{fileName}.mid(4, fileName.size()-8).toInt(); + lastImage = qMax(lastImage, imgNumber); + } + + path = QString("img_%1.jpg").arg(lastImage+1, + 4, //fieldWidth + 10, + QLatin1Char('0')); + } + + m_session->captureImage(m_lastId, path); + + return m_lastId; +} + +void QGstreamerImageCaptureControl::cancelCapture() +{ + +} + +void QGstreamerImageCaptureControl::updateState() +{ + bool ready = (m_session->state() == QGstreamerCaptureSession::PreviewState) && + (m_session->captureMode() & QGstreamerCaptureSession::Image); + + if (m_ready != ready) { + emit readyForCaptureChanged(m_ready = ready); + } +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol_p.h new file mode 100644 index 000000000..3bdf96f4a --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapturecontrol_p.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 QGSTREAMERIMAGECAPTURECONTROL_H +#define QGSTREAMERIMAGECAPTURECONTROL_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.h> +#include "qgstreamercapturesession_p.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerImageCaptureControl : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + QGstreamerImageCaptureControl(QGstreamerCaptureSession *session); + virtual ~QGstreamerImageCaptureControl(); + + QCameraImageCapture::DriveMode driveMode() const override { return QCameraImageCapture::SingleImageCapture; } + void setDriveMode(QCameraImageCapture::DriveMode) override {} + + bool isReadyForCapture() const override; + int capture(const QString &fileName) override; + void cancelCapture() override; + + QCameraImageCapture::CaptureDestinations captureDestination() const override { return QCameraImageCapture::CaptureToBuffer; } + virtual void setCaptureDestination(QCameraImageCapture::CaptureDestinations /*destination*/) override {} + +private slots: + void updateState(); + +private: + QGstreamerCaptureSession *m_session; + bool m_ready; + int m_lastId; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURECORNTROL_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode.cpp new file mode 100644 index 000000000..f64fd235a --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** 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 "qgstreamerimageencode_p.h" +#include "qgstreamercapturesession_p.h" + +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerImageEncode::QGstreamerImageEncode(QGstreamerCaptureSession *session) + :QImageEncoderControl(session), m_session(session) +{ +} + +QGstreamerImageEncode::~QGstreamerImageEncode() +{ +} + +QList<QSize> QGstreamerImageEncode::supportedResolutions(const QImageEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = m_session->videoInput() != 0; + + return m_session->videoInput() ? m_session->videoInput()->supportedResolutions() : QList<QSize>(); +} + +QStringList QGstreamerImageEncode::supportedImageCodecs() const +{ + return QStringList() << "jpeg"; +} + +QString QGstreamerImageEncode::imageCodecDescription(const QString &codecName) const +{ + if (codecName == "jpeg") + return tr("JPEG image encoder"); + + return QString(); +} + +QImageEncoderSettings QGstreamerImageEncode::imageSettings() const +{ + return m_settings; +} + +void QGstreamerImageEncode::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_settings != settings) { + m_settings = settings; + emit settingsChanged(); + } +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode_p.h new file mode 100644 index 000000000..eef51eee0 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimageencode_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** 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 QGSTREAMERIMAGEENCODE_H +#define QGSTREAMERIMAGEENCODE_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> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerCaptureSession; + +class QGstreamerImageEncode : public QImageEncoderControl +{ + Q_OBJECT +public: + QGstreamerImageEncode(QGstreamerCaptureSession *session); + virtual ~QGstreamerImageEncode(); + + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings = QImageEncoderSettings(), + bool *continuous = 0) const override; + + QStringList supportedImageCodecs() const override; + QString imageCodecDescription(const QString &codecName) const override; + + QImageEncoderSettings imageSettings() const override; + void setImageSettings(const QImageEncoderSettings &settings) override; + +Q_SIGNALS: + void settingsChanged(); + +private: + QImageEncoderSettings m_settings; + + QGstreamerCaptureSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp new file mode 100644 index 000000000..6375a2207 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** 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 "qgstreamermediacontainercontrol_p.h" + +#include <private/qgstutils_p.h> + +#include <QtCore/qdebug.h> + +QGstreamerMediaContainerControl::QGstreamerMediaContainerControl(QObject *parent) + :QMediaContainerControl(parent) + , m_containers(QGstCodecsInfo::Muxer) +{ +} + +QSet<QString> QGstreamerMediaContainerControl::supportedStreamTypes(const QString &container) const +{ + return m_containers.supportedStreamTypes(container); +} + +QString QGstreamerMediaContainerControl::containerExtension() const +{ + return QGstUtils::fileExtensionForMimeType(m_format); +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol_p.h new file mode 100644 index 000000000..279dbe931 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamermediacontainercontrol_p.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 QGSTREAMERMEDIACONTAINERCONTROL_H +#define QGSTREAMERMEDIACONTAINERCONTROL_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> +#include <QtCore/qstringlist.h> +#include <QtCore/qset.h> + +#include <private/qgstcodecsinfo_p.h> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerMediaContainerControl : public QMediaContainerControl +{ +Q_OBJECT +public: + QGstreamerMediaContainerControl(QObject *parent); + ~QGstreamerMediaContainerControl() {} + + QStringList supportedContainers() const override { return m_containers.supportedCodecs(); } + QString containerFormat() const override { return m_format; } + void setContainerFormat(const QString &formatMimeType) override { m_format = formatMimeType; } + + QString containerDescription(const QString &formatMimeType) const override { return m_containers.codecDescription(formatMimeType); } + + QByteArray formatElementName() const { return m_containers.codecElement(containerFormat()); } + + QSet<QString> supportedStreamTypes(const QString &container) const; + + QString containerExtension() const; + +private: + QString m_format; + QGstCodecsInfo m_containers; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERMEDIACONTAINERCONTROL_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp new file mode 100644 index 000000000..4477973f8 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp @@ -0,0 +1,372 @@ +/**************************************************************************** +** +** 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 "qgstreamerrecordercontrol_p.h" +#include "qgstreameraudioencode_p.h" +#include "qgstreamervideoencode_p.h" +#include "qgstreamermediacontainercontrol_p.h" +#include <QtCore/QDebug> +#include <QtGui/qdesktopservices.h> +#include <QStandardPaths> + +QGstreamerRecorderControl::QGstreamerRecorderControl(QGstreamerCaptureSession *session) + :QMediaRecorderControl(session), + m_session(session), + m_state(QMediaRecorder::StoppedState), + m_status(QMediaRecorder::UnloadedStatus) +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), SLOT(updateStatus())); + connect(m_session, SIGNAL(error(int,QString)), SLOT(handleSessionError(int,QString))); + connect(m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedChanged(bool)), SIGNAL(mutedChanged(bool))); + connect(m_session, SIGNAL(volumeChanged(qreal)), SIGNAL(volumeChanged(qreal))); + m_hasPreviewState = m_session->captureMode() != QGstreamerCaptureSession::Audio; +} + +QGstreamerRecorderControl::~QGstreamerRecorderControl() +{ +} + +QUrl QGstreamerRecorderControl::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool QGstreamerRecorderControl::setOutputLocation(const QUrl &sink) +{ + m_outputLocation = sink; + m_session->setOutputLocation(sink); + return true; +} + + +QMediaRecorder::State QGstreamerRecorderControl::state() const +{ + return m_state; +} + +QMediaRecorder::Status QGstreamerRecorderControl::status() const +{ + static QMediaRecorder::Status statusTable[3][3] = { + //Stopped recorder state: + { QMediaRecorder::LoadedStatus, QMediaRecorder::FinalizingStatus, QMediaRecorder::FinalizingStatus }, + //Recording recorder state: + { QMediaRecorder::StartingStatus, QMediaRecorder::RecordingStatus, QMediaRecorder::PausedStatus }, + //Paused recorder state: + { QMediaRecorder::StartingStatus, QMediaRecorder::RecordingStatus, QMediaRecorder::PausedStatus } + }; + + QMediaRecorder::State sessionState = QMediaRecorder::StoppedState; + + switch ( m_session->state() ) { + case QGstreamerCaptureSession::RecordingState: + sessionState = QMediaRecorder::RecordingState; + break; + case QGstreamerCaptureSession::PausedState: + sessionState = QMediaRecorder::PausedState; + break; + case QGstreamerCaptureSession::PreviewState: + case QGstreamerCaptureSession::StoppedState: + sessionState = QMediaRecorder::StoppedState; + break; + } + + return statusTable[m_state][sessionState]; +} + +void QGstreamerRecorderControl::updateStatus() +{ + QMediaRecorder::Status newStatus = status(); + if (m_status != newStatus) { + m_status = newStatus; + emit statusChanged(m_status); + // If stop has been called and session state became stopped. + if (m_status == QMediaRecorder::LoadedStatus) + emit stateChanged(m_state); + } +} + +void QGstreamerRecorderControl::handleSessionError(int code, const QString &description) +{ + emit error(code, description); + stop(); +} + +qint64 QGstreamerRecorderControl::duration() const +{ + return m_session->duration(); +} + +void QGstreamerRecorderControl::setState(QMediaRecorder::State state) +{ + switch (state) { + case QMediaRecorder::StoppedState: + stop(); + break; + case QMediaRecorder::PausedState: + pause(); + break; + case QMediaRecorder::RecordingState: + record(); + break; + } +} + +void QGstreamerRecorderControl::record() +{ + if (m_state == QMediaRecorder::RecordingState) + return; + + m_state = QMediaRecorder::RecordingState; + + if (m_outputLocation.isEmpty()) { + QString container = m_session->mediaContainerControl()->containerExtension(); + if (container.isEmpty()) + container = "raw"; + + m_session->setOutputLocation(QUrl(generateFileName(defaultDir(), container))); + } + + m_session->dumpGraph("before-record"); + if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { + m_session->setState(QGstreamerCaptureSession::RecordingState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); + + m_session->dumpGraph("after-record"); + + emit stateChanged(m_state); + updateStatus(); + + emit actualLocationChanged(m_session->outputLocation()); +} + +void QGstreamerRecorderControl::pause() +{ + if (m_state == QMediaRecorder::PausedState) + return; + + m_state = QMediaRecorder::PausedState; + + m_session->dumpGraph("before-pause"); + if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { + m_session->setState(QGstreamerCaptureSession::PausedState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); + + emit stateChanged(m_state); + updateStatus(); +} + +void QGstreamerRecorderControl::stop() +{ + if (m_state == QMediaRecorder::StoppedState) + return; + + m_state = QMediaRecorder::StoppedState; + + if (!m_hasPreviewState) { + m_session->setState(QGstreamerCaptureSession::StoppedState); + } else { + if (m_session->state() != QGstreamerCaptureSession::StoppedState) + m_session->setState(QGstreamerCaptureSession::PreviewState); + } + + updateStatus(); +} + +void QGstreamerRecorderControl::applySettings() +{ + //Check the codecs are compatible with container, + //and choose the compatible codecs/container if omitted + QGstreamerAudioEncode *audioEncodeControl = m_session->audioEncodeControl(); + QGstreamerVideoEncode *videoEncodeControl = m_session->videoEncodeControl(); + QGstreamerMediaContainerControl *mediaContainerControl = m_session->mediaContainerControl(); + + bool needAudio = m_session->captureMode() & QGstreamerCaptureSession::Audio; + bool needVideo = m_session->captureMode() & QGstreamerCaptureSession::Video; + + QStringList containerCandidates; + if (mediaContainerControl->containerFormat().isEmpty()) + containerCandidates = mediaContainerControl->supportedContainers(); + else + containerCandidates << mediaContainerControl->containerFormat(); + + + QStringList audioCandidates; + if (needAudio) { + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + if (audioSettings.codec().isEmpty()) + audioCandidates = audioEncodeControl->supportedAudioCodecs(); + else + audioCandidates << audioSettings.codec(); + } + + QStringList videoCandidates; + if (needVideo) { + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + if (videoSettings.codec().isEmpty()) + videoCandidates = videoEncodeControl->supportedVideoCodecs(); + else + videoCandidates << videoSettings.codec(); + } + + QString container; + QString audioCodec; + QString videoCodec; + + for (const QString &containerCandidate : qAsConst(containerCandidates)) { + QSet<QString> supportedTypes = mediaContainerControl->supportedStreamTypes(containerCandidate); + + audioCodec.clear(); + videoCodec.clear(); + + if (needAudio) { + bool found = false; + for (const QString &audioCandidate : qAsConst(audioCandidates)) { + QSet<QString> audioTypes = audioEncodeControl->supportedStreamTypes(audioCandidate); + if (audioTypes.intersects(supportedTypes)) { + found = true; + audioCodec = audioCandidate; + break; + } + } + if (!found) + continue; + } + + if (needVideo) { + bool found = false; + for (const QString &videoCandidate : qAsConst(videoCandidates)) { + QSet<QString> videoTypes = videoEncodeControl->supportedStreamTypes(videoCandidate); + if (videoTypes.intersects(supportedTypes)) { + found = true; + videoCodec = videoCandidate; + break; + } + } + if (!found) + continue; + } + + container = containerCandidate; + break; + } + + if (container.isEmpty()) { + emit error(QMediaRecorder::FormatError, tr("Not compatible codecs and container format.")); + } else { + mediaContainerControl->setContainerFormat(container); + + if (needAudio) { + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + audioSettings.setCodec(audioCodec); + audioEncodeControl->setAudioSettings(audioSettings); + } + + if (needVideo) { + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + videoSettings.setCodec(videoCodec); + videoEncodeControl->setVideoSettings(videoSettings); + } + } +} + + +bool QGstreamerRecorderControl::isMuted() const +{ + return m_session->isMuted(); +} + +qreal QGstreamerRecorderControl::volume() const +{ + return m_session->volume(); +} + +void QGstreamerRecorderControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +void QGstreamerRecorderControl::setVolume(qreal volume) +{ + m_session->setVolume(volume); +} + +QDir QGstreamerRecorderControl::defaultDir() const +{ + QStringList dirCandidates; + + if (m_session->captureMode() & QGstreamerCaptureSession::Video) + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::MoviesLocation); + else + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::MusicLocation); + + dirCandidates << QDir::home().filePath("Documents"); + dirCandidates << QDir::home().filePath("My Documents"); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + for (const QString &path : qAsConst(dirCandidates)) { + QDir dir(path); + if (dir.exists() && QFileInfo(path).isWritable()) + return dir; + } + + return QDir(); +} + +QString QGstreamerRecorderControl::generateFileName(const QDir &dir, const QString &ext) const +{ + + int lastClip = 0; + const auto list = dir.entryList(QStringList() << QString("clip_*.%1").arg(ext)); + for (const QString &fileName : list) { + int imgNumber = QStringView{fileName}.mid(5, fileName.size()-6-ext.length()).toInt(); + lastClip = qMax(lastClip, imgNumber); + } + + QString name = QString("clip_%1.%2").arg(lastClip+1, + 4, //fieldWidth + 10, + QLatin1Char('0')).arg(ext); + + return dir.absoluteFilePath(name); +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol_p.h new file mode 100644 index 000000000..dfe0299a4 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerrecordercontrol_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 QGSTREAMERRECORDERCONTROL_H +#define QGSTREAMERRECORDERCONTROL_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/QDir> + +#include <qmediarecordercontrol.h> +#include "qgstreamercapturesession_p.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerRecorderControl : public QMediaRecorderControl +{ + Q_OBJECT + +public: + QGstreamerRecorderControl(QGstreamerCaptureSession *session); + virtual ~QGstreamerRecorderControl(); + + QUrl outputLocation() const override; + bool setOutputLocation(const QUrl &sink) 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 slots: + void setState(QMediaRecorder::State state) override; + void record(); + void pause(); + void stop(); + void setMuted(bool) override; + void setVolume(qreal volume) override; + +private slots: + void updateStatus(); + void handleSessionError(int code, const QString &description); + +private: + QDir defaultDir() const; + QString generateFileName(const QDir &dir, const QString &ext) const; + + QUrl m_outputLocation; + QGstreamerCaptureSession *m_session; + QMediaRecorder::State m_state; + QMediaRecorder::Status m_status; + bool m_hasPreviewState; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERCAPTURECORNTROL_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input.cpp new file mode 100644 index 000000000..473700e64 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input.cpp @@ -0,0 +1,286 @@ +/**************************************************************************** +** +** 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 "qgstreamerv4l2input_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +#include <private/qcore_unix_p.h> +#include <linux/videodev2.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE +static inline uint qHash(const QSize& key) { return uint(key.width()*256+key.height()); } + +static bool operator<(const QSize &s1, const QSize s2) +{ + return s1.width()*s1.height() < s2.width()*s2.height(); +} +QT_END_NAMESPACE + +QGstreamerV4L2Input::QGstreamerV4L2Input(QObject *parent) + :QObject(parent) +{ +} + +QGstreamerV4L2Input::~QGstreamerV4L2Input() +{ +} + +GstElement *QGstreamerV4L2Input::buildElement() +{ + GstElement *camera = gst_element_factory_make("v4l2src", "camera_source"); + if (camera && !m_device.isEmpty() ) + g_object_set(G_OBJECT(camera), "device", m_device.constData(), NULL); + + return camera; +} + +void QGstreamerV4L2Input::setDevice(const QByteArray &newDevice) +{ + if (m_device != newDevice) { + m_device = newDevice; + updateSupportedResolutions(newDevice); + } +} + +void QGstreamerV4L2Input::setDevice(const QString &device) +{ + setDevice(QFile::encodeName(device)); +} + +void QGstreamerV4L2Input::updateSupportedResolutions(const QByteArray &device) +{ + m_frameRates.clear(); + m_resolutions.clear(); + m_ratesByResolution.clear(); + + QSet<QSize> allResolutions; + QSet<int> allFrameRates; + + QFile f(device); + + if (!f.open(QFile::ReadOnly)) + return; + + int fd = f.handle(); + + //get the list of formats: + QList<quint32> supportedFormats; + + { + v4l2_fmtdesc fmt; + memset(&fmt, 0, sizeof(v4l2_fmtdesc)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int sanity = 0; + + for (fmt.index = 0;; fmt.index++) { + if (sanity++ > 8) + break; + if( ::ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == -1) { + if(errno == EINVAL) + break; + } + supportedFormats.append(fmt.pixelformat); + } + } + + QList<QSize> commonSizes; + commonSizes << QSize(128, 96) + <<QSize(160,120) + <<QSize(176, 144) + <<QSize(320, 240) + <<QSize(352, 288) + <<QSize(640, 480) + <<QSize(1024, 768) + <<QSize(1280, 1024) + <<QSize(1600, 1200) + <<QSize(1920, 1200) + <<QSize(2048, 1536) + <<QSize(2560, 1600) + <<QSize(2580, 1936); + + QList<int> commonRates; + commonRates << 05*1000 << 75*1000 << 10*1000 << 15*1000 << 20*1000 + << 24*1000 << 25*1000 << 30*1000 << 50*1000 << 60*1000; + + + //get the list of resolutions: + + for (quint32 format : qAsConst(supportedFormats)) { + struct v4l2_frmsizeenum formatSize; + memset(&formatSize, 0, sizeof(formatSize)); + formatSize.pixel_format = format; + + QList<QSize> sizeList; + + if (0) { + char formatStr[5]; + memcpy(formatStr, &format, 4); + formatStr[4] = 0; + //qDebug() << "trying format" << formatStr; + } + + for (int i=0;;i++) { + formatSize.index = i; + if (ioctl (fd, VIDIOC_ENUM_FRAMESIZES, &formatSize) < 0) + break; + + if (formatSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + sizeList.append(QSize(formatSize.discrete.width, formatSize.discrete.height)); + } else { + + for (const QSize& candidate : qAsConst(commonSizes)) { + if (candidate.width() <= (int)formatSize.stepwise.max_width && + candidate.height() >= (int)formatSize.stepwise.min_width && + candidate.width() % formatSize.stepwise.step_width == 0 && + candidate.height() <= (int)formatSize.stepwise.max_height && + candidate.height() >= (int)formatSize.stepwise.min_height && + candidate.height() % formatSize.stepwise.step_height == 0) { + sizeList.append(candidate); + } + } + + if (!sizeList.contains(QSize(formatSize.stepwise.min_width, formatSize.stepwise.min_height))) + sizeList.prepend(QSize(formatSize.stepwise.min_width, formatSize.stepwise.min_height)); + + if (!sizeList.contains(QSize(formatSize.stepwise.max_width, formatSize.stepwise.max_height))) + sizeList.append(QSize(formatSize.stepwise.max_width, formatSize.stepwise.max_height)); + + break; //stepwise values are returned only for index 0 + } + + } + + //and frameRates for each resolution. + + for (const QSize &s : qAsConst(sizeList)) { + allResolutions.insert(s); + + struct v4l2_frmivalenum formatInterval; + memset(&formatInterval, 0, sizeof(formatInterval)); + formatInterval.pixel_format = format; + formatInterval.width = s.width(); + formatInterval.height = s.height(); + + QList<int> frameRates; //in 1/1000 of fps + + for (int i=0; ; i++) { + formatInterval.index = i; + + if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &formatInterval) < 0) + break; + + if (formatInterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) { + //converts seconds to fps*1000 + if (formatInterval.discrete.numerator) + frameRates.append(qRound(formatInterval.discrete.denominator*1000.0 / formatInterval.discrete.numerator)); + } else { + if (formatInterval.stepwise.min.numerator == 0 || + formatInterval.stepwise.max.numerator == 0) { + qWarning() << "received invalid frame interval"; + break; + } + + + int minRate = qRound(formatInterval.stepwise.min.denominator*1000.0 / + formatInterval.stepwise.min.numerator); + + int maxRate = qRound(formatInterval.stepwise.max.denominator*1000.0 / + formatInterval.stepwise.max.numerator); + + + for (int candidate : qAsConst(commonRates)) { + if (candidate >= minRate && candidate <= maxRate) + frameRates.append(candidate); + } + + if (!frameRates.contains(minRate)) + frameRates.prepend(minRate); + + if (!frameRates.contains(maxRate)) + frameRates.append(maxRate); + + break; //stepwise values are returned only for index 0 + } + } + allFrameRates.unite(QSet<int>{frameRates.constBegin(), frameRates.constEnd()}); + m_ratesByResolution[s].unite(QSet<int>{frameRates.constBegin(), frameRates.constEnd()}); + } + } + + f.close(); + + for (int rate : qAsConst(allFrameRates)) { + m_frameRates.append(rate/1000.0); + } + + std::sort(m_frameRates.begin(), m_frameRates.end()); + + m_resolutions = QList<QSize>{allResolutions.constBegin(), allResolutions.constEnd()}; + std::sort(m_resolutions.begin(), m_resolutions.end()); + + //qDebug() << "frame rates:" << m_frameRates; + //qDebug() << "resolutions:" << m_resolutions; +} + + +QList<qreal> QGstreamerV4L2Input::supportedFrameRates(const QSize &frameSize) const +{ + if (frameSize.isEmpty()) + return m_frameRates; + else { + QList<qreal> res; + const auto rates = m_ratesByResolution[frameSize]; + res.reserve(rates.size()); + for (int rate : rates) { + res.append(rate/1000.0); + } + return res; + } +} + +QList<QSize> QGstreamerV4L2Input::supportedResolutions(qreal frameRate) const +{ + Q_UNUSED(frameRate); + return m_resolutions; +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input_p.h new file mode 100644 index 000000000..f2a3f075a --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerv4l2input_p.h @@ -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$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERV4L2INPUT_H +#define QGSTREAMERV4L2INPUT_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/qhash.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qlist.h> +#include <QtCore/qsize.h> +#include "qgstreamercapturesession_p.h" + +QT_BEGIN_NAMESPACE + +class QGstreamerV4L2Input : public QObject, public QGstreamerVideoInput +{ + Q_OBJECT +public: + QGstreamerV4L2Input(QObject *parent = 0); + virtual ~QGstreamerV4L2Input(); + + GstElement *buildElement() override; + + QList<qreal> supportedFrameRates(const QSize &frameSize = QSize()) const override; + QList<QSize> supportedResolutions(qreal frameRate = -1) const override; + + QByteArray device() const; + +public slots: + void setDevice(const QByteArray &device); + void setDevice(const QString &device); + +private: + void updateSupportedResolutions(const QByteArray &device); + + QList<qreal> m_frameRates; + QList<QSize> m_resolutions; + + QHash<QSize, QSet<int> > m_ratesByResolution; + + QByteArray m_device; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERV4L2INPUT_H diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode.cpp new file mode 100644 index 000000000..ae46f2422 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode.cpp @@ -0,0 +1,296 @@ +/**************************************************************************** +** +** 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 "qgstreamervideoencode_p.h" +#include "qgstreamercapturesession_p.h" +#include "qgstreamermediacontainercontrol_p.h" +#include <private/qgstutils_p.h> +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerVideoEncode::QGstreamerVideoEncode(QGstreamerCaptureSession *session) + :QVideoEncoderSettingsControl(session), m_session(session) + , m_codecs(QGstCodecsInfo::VideoEncoder) +{ +} + +QGstreamerVideoEncode::~QGstreamerVideoEncode() +{ +} + +QList<QSize> QGstreamerVideoEncode::supportedResolutions(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = m_session->videoInput() != 0; + + return m_session->videoInput() ? m_session->videoInput()->supportedResolutions() : QList<QSize>(); +} + +QList< qreal > QGstreamerVideoEncode::supportedFrameRates(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = false; + + return m_session->videoInput() ? m_session->videoInput()->supportedFrameRates() : QList<qreal>(); +} + +QStringList QGstreamerVideoEncode::supportedVideoCodecs() const +{ + return m_codecs.supportedCodecs(); +} + +QString QGstreamerVideoEncode::videoCodecDescription(const QString &codecName) const +{ + return m_codecs.codecDescription(codecName); +} + +QStringList QGstreamerVideoEncode::supportedEncodingOptions(const QString &codec) const +{ + return m_codecs.codecOptions(codec); +} + +QVariant QGstreamerVideoEncode::encodingOption(const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void QGstreamerVideoEncode::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QVideoEncoderSettings QGstreamerVideoEncode::videoSettings() const +{ + return m_videoSettings; +} + +void QGstreamerVideoEncode::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoSettings = settings; +} + +GstElement *QGstreamerVideoEncode::createEncoder() +{ + QString codec = m_videoSettings.codec(); + GstElement *encoderElement = gst_element_factory_make(m_codecs.codecElement(codec).constData(), "video-encoder"); + if (!encoderElement) + return 0; + + GstBin *encoderBin = GST_BIN(gst_bin_new("video-encoder-bin")); + + GstElement *sinkCapsFilter = gst_element_factory_make("capsfilter", "capsfilter-video"); + GstElement *srcCapsFilter = gst_element_factory_make("capsfilter", "capsfilter-video"); + gst_bin_add_many(encoderBin, sinkCapsFilter, srcCapsFilter, NULL); + + GstElement *colorspace = gst_element_factory_make("videoconvert", NULL); + gst_bin_add(encoderBin, colorspace); + gst_bin_add(encoderBin, encoderElement); + + gst_element_link_many(sinkCapsFilter, colorspace, encoderElement, srcCapsFilter, NULL); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(sinkCapsFilter, "sink"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(srcCapsFilter, "src"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + if (encoderElement) { + if (m_videoSettings.encodingMode() == QMultimedia::ConstantQualityEncoding) { + QMultimedia::EncodingQuality qualityValue = m_videoSettings.quality(); + + if (codec == QLatin1String("video/x-h264")) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 4, NULL); + int qualityTable[] = { + 50, //VeryLow + 35, //Low + 21, //Normal + 15, //High + 8 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quantizer", qualityTable[qualityValue], NULL); + } else if (codec == QLatin1String("video/x-xvid")) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 3, NULL); + int qualityTable[] = { + 32, //VeryLow + 12, //Low + 5, //Normal + 3, //High + 2 //VeryHigh + }; + int quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (codec.startsWith(QLatin1String("video/mpeg"))) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 2, NULL); + //quant from 1 to 30, default ~3 + double qualityTable[] = { + 20, //VeryLow + 8.0, //Low + 3.0, //Normal + 2.5, //High + 2.0 //VeryHigh + }; + double quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (codec == QLatin1String("video/x-theora")) { + int qualityTable[] = { + 8, //VeryLow + 16, //Low + 32, //Normal + 45, //High + 60 //VeryHigh + }; + //quality from 0 to 63 + int quality = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quality", quality, NULL); + } + } else { + int bitrate = m_videoSettings.bitRate(); + if (bitrate > 0) { + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString,QVariant> options = m_options.value(codec); + for (auto it = options.cbegin(), end = options.cend(); it != end; ++it) { + const QString &option = it.key(); + const QVariant &value = it.value(); + + switch (value.typeId()) { + case QMetaType::Int: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toInt(), NULL); + break; + case QMetaType::Bool: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toBool(), NULL); + break; + case QMetaType::Double: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toDouble(), NULL); + break; + case QMetaType::QString: + g_object_set(G_OBJECT(encoderElement), option.toLatin1(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + + } + } + + if (!m_videoSettings.resolution().isEmpty() || m_videoSettings.frameRate() > 0.001) { + GstCaps *caps = QGstUtils::videoFilterCaps(); + + if (!m_videoSettings.resolution().isEmpty()) { + gst_caps_set_simple( + caps, + "width", G_TYPE_INT, m_videoSettings.resolution().width(), + "height", G_TYPE_INT, m_videoSettings.resolution().height(), + NULL); + } + + if (m_videoSettings.frameRate() > 0.001) { + QPair<int,int> rate = rateAsRational(); + gst_caps_set_simple( + caps, + "framerate", GST_TYPE_FRACTION, rate.first, rate.second, + NULL); + } + + //qDebug() << "set video caps filter:" << gst_caps_to_string(caps); + + g_object_set(G_OBJECT(sinkCapsFilter), "caps", caps, NULL); + + gst_caps_unref(caps); + } + + // Some encoders support several codecs. Setting a caps filter downstream with the desired + // codec (which is actually a string representation of the caps) will make sure we use the + // correct codec. + GstCaps *caps = gst_caps_from_string(codec.toUtf8().constData()); + g_object_set(G_OBJECT(srcCapsFilter), "caps", caps, NULL); + gst_caps_unref(caps); + + return GST_ELEMENT(encoderBin); +} + +QPair<int,int> QGstreamerVideoEncode::rateAsRational() const +{ + qreal frameRate = m_videoSettings.frameRate(); + + if (frameRate > 0.001) { + //convert to rational number + QList<int> denumCandidates; + denumCandidates << 1 << 2 << 3 << 5 << 10 << 1001 << 1000; + + qreal error = 1.0; + int num = 1; + int denum = 1; + + for (int curDenum : qAsConst(denumCandidates)) { + int curNum = qRound(frameRate*curDenum); + qreal curError = qAbs(qreal(curNum)/curDenum - frameRate); + + if (curError < error) { + error = curError; + num = curNum; + denum = curDenum; + } + + if (curError < 1e-8) + break; + } + + return QPair<int,int>(num,denum); + } + + return QPair<int,int>(); +} + + +QSet<QString> QGstreamerVideoEncode::supportedStreamTypes(const QString &codecName) const +{ + return m_codecs.supportedStreamTypes(codecName); +} diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode_p.h b/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode_p.h new file mode 100644 index 000000000..5cc38e25d --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamervideoencode_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** 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 QGSTREAMERVIDEOENCODE_H +#define QGSTREAMERVIDEOENCODE_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> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <private/qgstcodecsinfo_p.h> + +#include <gst/gst.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerCaptureSession; + +class QGstreamerVideoEncode : public QVideoEncoderSettingsControl +{ + Q_OBJECT +public: + QGstreamerVideoEncode(QGstreamerCaptureSession *session); + virtual ~QGstreamerVideoEncode(); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const override; + + QList< qreal > supportedFrameRates(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const override; + + QPair<int,int> rateAsRational() const; + + QStringList supportedVideoCodecs() const override; + QString videoCodecDescription(const QString &codecName) const override; + + QVideoEncoderSettings videoSettings() const override; + void setVideoSettings(const QVideoEncoderSettings &settings) override; + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + +private: + QGstreamerCaptureSession *m_session; + + QGstCodecsInfo m_codecs; + + QVideoEncoderSettings m_videoSettings; + QMap<QString, QMap<QString, QVariant> > m_options; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/mediaplayer/mediaplayer.pri b/src/multimedia/platform/gstreamer/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..0e7502105 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/mediaplayer.pri @@ -0,0 +1,13 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/qgstreamerplayerservice_p.h \ + $$PWD/qgstreamerstreamscontrol_p.h \ + $$PWD/qgstreamermetadataprovider_p.h \ + $$PWD/qgstreamerplayerserviceplugin_p.h + +SOURCES += \ + $$PWD/qgstreamerplayerservice.cpp \ + $$PWD/qgstreamerstreamscontrol.cpp \ + $$PWD/qgstreamermetadataprovider.cpp \ + $$PWD/qgstreamerplayerserviceplugin.cpp diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp new file mode 100644 index 000000000..578bbb8db --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp @@ -0,0 +1,184 @@ +/**************************************************************************** +** +** 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 "qgstreamermetadataprovider_p.h" +#include <private/qgstreamerplayersession_p.h> +#include <QDebug> +#include <QtMultimedia/qmediametadata.h> + +#include <gst/gstversion.h> +#include <private/qgstutils_p.h> + +QT_BEGIN_NAMESPACE + +typedef QMap<QByteArray, QString> QGstreamerMetaDataKeyLookup; +Q_GLOBAL_STATIC(QGstreamerMetaDataKeyLookup, metadataKeys) + +static const QGstreamerMetaDataKeyLookup *qt_gstreamerMetaDataKeys() +{ + if (metadataKeys->isEmpty()) { + metadataKeys->insert(GST_TAG_TITLE, QMediaMetaData::Title); + //metadataKeys->insert(0, QMediaMetaData::SubTitle); + //metadataKeys->insert(0, QMediaMetaData::Author); + metadataKeys->insert(GST_TAG_COMMENT, QMediaMetaData::Comment); + metadataKeys->insert(GST_TAG_DESCRIPTION, QMediaMetaData::Description); + //metadataKeys->insert(0, QMediaMetaData::Category); + metadataKeys->insert(GST_TAG_GENRE, QMediaMetaData::Genre); + metadataKeys->insert("year", QMediaMetaData::Year); + //metadataKeys->insert(0, QMediaMetaData::UserRating); + + metadataKeys->insert(GST_TAG_LANGUAGE_CODE, QMediaMetaData::Language); + + metadataKeys->insert(GST_TAG_ORGANIZATION, QMediaMetaData::Publisher); + metadataKeys->insert(GST_TAG_COPYRIGHT, QMediaMetaData::Copyright); + //metadataKeys->insert(0, QMediaMetaData::ParentalRating); + //metadataKeys->insert(0, QMediaMetaData::RatingOrganisation); + + // Media + //metadataKeys->insert(0, QMediaMetaData::Size); + //metadataKeys->insert(0,QMediaMetaData::MediaType ); + metadataKeys->insert(GST_TAG_DURATION, QMediaMetaData::Duration); + + // Audio + metadataKeys->insert(GST_TAG_BITRATE, QMediaMetaData::AudioBitRate); + metadataKeys->insert(GST_TAG_AUDIO_CODEC, QMediaMetaData::AudioCodec); + //metadataKeys->insert(0, QMediaMetaData::ChannelCount); + //metadataKeys->insert(0, QMediaMetaData::SampleRate); + + // Music + metadataKeys->insert(GST_TAG_ALBUM, QMediaMetaData::AlbumTitle); + metadataKeys->insert(GST_TAG_ALBUM_ARTIST, QMediaMetaData::AlbumArtist); + metadataKeys->insert(GST_TAG_ARTIST, QMediaMetaData::ContributingArtist); + //metadataKeys->insert(0, QMediaMetaData::Conductor); + //metadataKeys->insert(0, QMediaMetaData::Lyrics); + //metadataKeys->insert(0, QMediaMetaData::Mood); + metadataKeys->insert(GST_TAG_TRACK_NUMBER, QMediaMetaData::TrackNumber); + + //metadataKeys->insert(0, QMediaMetaData::CoverArtUrlSmall); + //metadataKeys->insert(0, QMediaMetaData::CoverArtUrlLarge); + metadataKeys->insert(GST_TAG_PREVIEW_IMAGE, QMediaMetaData::ThumbnailImage); + metadataKeys->insert(GST_TAG_IMAGE, QMediaMetaData::CoverArtImage); + + // Image/Video + metadataKeys->insert("resolution", QMediaMetaData::Resolution); + metadataKeys->insert("pixel-aspect-ratio", QMediaMetaData::PixelAspectRatio); + metadataKeys->insert(GST_TAG_IMAGE_ORIENTATION, QMediaMetaData::Orientation); + + // Video + //metadataKeys->insert(0, QMediaMetaData::VideoFrameRate); + //metadataKeys->insert(0, QMediaMetaData::VideoBitRate); + metadataKeys->insert(GST_TAG_VIDEO_CODEC, QMediaMetaData::VideoCodec); + + //metadataKeys->insert(0, QMediaMetaData::PosterUrl); + + // Movie + //metadataKeys->insert(0, QMediaMetaData::ChapterNumber); + //metadataKeys->insert(0, QMediaMetaData::Director); + metadataKeys->insert(GST_TAG_PERFORMER, QMediaMetaData::LeadPerformer); + //metadataKeys->insert(0, QMediaMetaData::Writer); + + // Photos + //metadataKeys->insert(0, QMediaMetaData::CameraManufacturer); + //metadataKeys->insert(0, QMediaMetaData::CameraModel); + //metadataKeys->insert(0, QMediaMetaData::Event); + //metadataKeys->insert(0, QMediaMetaData::Subject); + } + + return metadataKeys; +} + +QGstreamerMetaDataProvider::QGstreamerMetaDataProvider(QGstreamerPlayerSession *session, QObject *parent) + :QMetaDataReaderControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(tagsChanged()), SLOT(updateTags())); +} + +QGstreamerMetaDataProvider::~QGstreamerMetaDataProvider() +{ +} + +bool QGstreamerMetaDataProvider::isMetaDataAvailable() const +{ + return !m_session->tags().isEmpty(); +} + +bool QGstreamerMetaDataProvider::isWritable() const +{ + return false; +} + +QVariant QGstreamerMetaDataProvider::metaData(const QString &key) const +{ + if (key == QMediaMetaData::Orientation) + return QGstUtils::fromGStreamerOrientation(m_tags.value(key)); + return m_tags.value(key); +} + +QStringList QGstreamerMetaDataProvider::availableMetaData() const +{ + return m_tags.keys(); +} + +void QGstreamerMetaDataProvider::updateTags() +{ + QVariantMap oldTags = m_tags; + m_tags.clear(); + bool changed = false; + + const auto tags = m_session->tags(); + for (auto i = tags.cbegin(), end = tags.cend(); i != end; ++i) { + //use gstreamer native keys for elements not in our key map + QString key = qt_gstreamerMetaDataKeys()->value(i.key(), i.key()); + m_tags.insert(key, i.value()); + if (i.value() != oldTags.value(key)) { + changed = true; + emit metaDataChanged(key, i.value()); + } + } + + if (oldTags.isEmpty() != m_tags.isEmpty()) { + emit metaDataAvailableChanged(isMetaDataAvailable()); + changed = true; + } + + if (changed) + emit metaDataChanged(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider_p.h b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider_p.h new file mode 100644 index 000000000..468ab060b --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamermetadataprovider_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 QGSTREAMERMETADATAPROVIDER_H +#define QGSTREAMERMETADATAPROVIDER_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.h> +#include <qvariant.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerMetaDataProvider : public QMetaDataReaderControl +{ + Q_OBJECT +public: + QGstreamerMetaDataProvider( QGstreamerPlayerSession *session, QObject *parent ); + virtual ~QGstreamerMetaDataProvider(); + + bool isMetaDataAvailable() const override; + bool isWritable() const; + + QVariant metaData(const QString &key) const override; + QStringList availableMetaData() const override; + +private slots: + void updateTags(); + +private: + QGstreamerPlayerSession *m_session = nullptr; + QVariantMap m_tags; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERMETADATAPROVIDER_H diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice.cpp b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice.cpp new file mode 100644 index 000000000..0c548c730 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice.cpp @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** 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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> + + +#include "qgstreamerplayerservice_p.h" +#include "qgstreamermetadataprovider_p.h" + +#include <private/qgstreamervideowindow_p.h> +#include <private/qgstreamervideorenderer_p.h> + +#include "qgstreamerstreamscontrol_p.h" +#include <private/qgstreameraudioprobecontrol_p.h> +#include <private/qgstreamervideoprobecontrol_p.h> +#include <private/qgstreamerplayersession_p.h> +#include <private/qgstreamerplayercontrol_p.h> + +QT_BEGIN_NAMESPACE + +QGstreamerPlayerService::QGstreamerPlayerService(QObject *parent) + : QMediaService(parent) +{ + m_session = new QGstreamerPlayerSession(this); + m_control = new QGstreamerPlayerControl(m_session, this); + m_metaData = new QGstreamerMetaDataProvider(m_session, this); + m_streamsControl = new QGstreamerStreamsControl(m_session,this); + m_videoRenderer = new QGstreamerVideoRenderer(this); + m_videoWindow = new QGstreamerVideoWindow(this); + // If the GStreamer video sink is not available, don't provide the video window control since + // it won't work anyway. + if (!m_videoWindow->videoSink()) { + delete m_videoWindow; + m_videoWindow = 0; + } +} + +QGstreamerPlayerService::~QGstreamerPlayerService() +{ +} + +QObject *QGstreamerPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name,QMediaPlayerControl_iid) == 0) + return m_control; + + if (qstrcmp(name,QMetaDataReaderControl_iid) == 0) + return m_metaData; + + if (qstrcmp(name,QMediaStreamsControl_iid) == 0) + return m_streamsControl; + + if (qstrcmp(name, QMediaVideoProbeControl_iid) == 0) { + if (!m_videoProbeControl) { + increaseVideoRef(); + m_videoProbeControl = new QGstreamerVideoProbeControl(this); + m_session->addProbe(m_videoProbeControl); + } + m_videoProbeControl->ref.ref(); + return m_videoProbeControl; + } + + if (qstrcmp(name, QMediaAudioProbeControl_iid) == 0) { + if (!m_audioProbeControl) { + m_audioProbeControl = new QGstreamerAudioProbeControl(this); + m_session->addProbe(m_audioProbeControl); + } + m_audioProbeControl->ref.ref(); + return m_audioProbeControl; + } + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoRendererControl_iid) == 0) + m_videoOutput = m_videoRenderer; + else if (qstrcmp(name, QVideoWindowControl_iid) == 0) + m_videoOutput = m_videoWindow; + + if (m_videoOutput) { + increaseVideoRef(); + m_control->setVideoOutput(m_videoOutput); + return m_videoOutput; + } + } + + return 0; +} + +void QGstreamerPlayerService::releaseControl(QObject *control) +{ + if (!control) + return; + + if (control == m_videoOutput) { + m_videoOutput = 0; + m_control->setVideoOutput(0); + decreaseVideoRef(); + } else if (control == m_videoProbeControl && !m_videoProbeControl->ref.deref()) { + m_session->removeProbe(m_videoProbeControl); + delete m_videoProbeControl; + m_videoProbeControl = 0; + decreaseVideoRef(); + } else if (control == m_audioProbeControl && !m_audioProbeControl->ref.deref()) { + m_session->removeProbe(m_audioProbeControl); + delete m_audioProbeControl; + m_audioProbeControl = 0; + } +} + +void QGstreamerPlayerService::increaseVideoRef() +{ + m_videoReferenceCount++; +} + +void QGstreamerPlayerService::decreaseVideoRef() +{ + m_videoReferenceCount--; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice_p.h b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice_p.h new file mode 100644 index 000000000..12550be16 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerservice_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 QGSTREAMERPLAYERSERVICE_H +#define QGSTREAMERPLAYERSERVICE_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/qobject.h> +#include <QtCore/qiodevice.h> + +#include <qmediaservice.h> + +QT_BEGIN_NAMESPACE +class QMediaPlayerControl; + +class QGstreamerMetaData; +class QGstreamerPlayerControl; +class QGstreamerPlayerSession; +class QGstreamerMetaDataProvider; +class QGstreamerStreamsControl; +class QGstreamerVideoRenderer; +class QGstreamerVideoWindow; +class QGStreamerAvailabilityControl; +class QGstreamerAudioProbeControl; +class QGstreamerVideoProbeControl; + +class QGstreamerPlayerService : public QMediaService +{ + Q_OBJECT +public: + QGstreamerPlayerService(QObject *parent = 0); + ~QGstreamerPlayerService(); + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *control) override; + +private: + QGstreamerPlayerControl *m_control = nullptr; + QGstreamerPlayerSession *m_session = nullptr; + QGstreamerMetaDataProvider *m_metaData = nullptr; + QGstreamerStreamsControl *m_streamsControl = nullptr; + + QGstreamerAudioProbeControl *m_audioProbeControl = nullptr; + QGstreamerVideoProbeControl *m_videoProbeControl = nullptr; + + QObject *m_videoOutput = nullptr; + QObject *m_videoRenderer = nullptr; + QGstreamerVideoWindow *m_videoWindow = nullptr; + + void increaseVideoRef(); + void decreaseVideoRef(); + int m_videoReferenceCount = 0; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin.cpp b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin.cpp new file mode 100644 index 000000000..4da251014 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin.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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include "qgstreamerplayerserviceplugin_p.h" + +//#define QT_SUPPORTEDMIMETYPES_DEBUG + +#include "qgstreamerplayerservice_p.h" +#include <private/qgstutils_p.h> + +QMediaService* QGstreamerPlayerServicePlugin::create(const QString &key) +{ + QGstUtils::initializeGst(); + + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new QGstreamerPlayerService; + + qWarning() << "Gstreamer service plugin: unsupported key:" << key; + return 0; +} + +void QGstreamerPlayerServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QMultimedia::SupportEstimate QGstreamerPlayerServicePlugin::hasSupport(const QString &mimeType, + const QStringList &codecs) const +{ + if (m_supportedMimeTypeSet.isEmpty()) + updateSupportedMimeTypes(); + + return QGstUtils::hasSupport(mimeType, codecs, m_supportedMimeTypeSet); +} + +static bool isDecoderOrDemuxer(GstElementFactory *factory) +{ + return gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DEMUXER) + || gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DECODER); +} + +void QGstreamerPlayerServicePlugin::updateSupportedMimeTypes() const +{ + m_supportedMimeTypeSet = QGstUtils::supportedMimeTypes(isDecoderOrDemuxer); +} + +QStringList QGstreamerPlayerServicePlugin::supportedMimeTypes() const +{ + return QStringList(); +} + diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin_p.h b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin_p.h new file mode 100644 index 000000000..8c4e6630c --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerplayerserviceplugin_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 QGSTREAMERPLAYERSERVICEPLUGIN_H +#define QGSTREAMERPLAYERSERVICEPLUGIN_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.h> +#include <QtCore/qset.h> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE + + +class QGstreamerPlayerServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedFormatsInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedFormatsInterface) +public: + QMediaService* create(const QString &key) override; + void release(QMediaService *service) override; + + QMultimedia::SupportEstimate hasSupport(const QString &mimeType, const QStringList &codecs) const override; + QStringList supportedMimeTypes() const override; + +private: + void updateSupportedMimeTypes() const; + + mutable QSet<QString> m_supportedMimeTypeSet; //for fast access +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERPLAYERSERVICEPLUGIN_H + diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp new file mode 100644 index 000000000..a4a2f46ec --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp @@ -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$ +** +****************************************************************************/ + +#include "qgstreamerstreamscontrol_p.h" +#include <private/qgstreamerplayersession_p.h> + +QGstreamerStreamsControl::QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent) + :QMediaStreamsControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(streamsChanged()), SIGNAL(streamsChanged())); +} + +QGstreamerStreamsControl::~QGstreamerStreamsControl() +{ +} + +int QGstreamerStreamsControl::streamCount() +{ + return m_session->streamCount(); +} + +QMediaStreamsControl::StreamType QGstreamerStreamsControl::streamType(int streamNumber) +{ + return m_session->streamType(streamNumber); +} + +QVariant QGstreamerStreamsControl::metaData(int streamNumber, const QString &key) +{ + return m_session->streamProperties(streamNumber).value(key); +} + +bool QGstreamerStreamsControl::isActive(int streamNumber) +{ + return streamNumber != -1 && streamNumber == m_session->activeStream(streamType(streamNumber)); +} + +void QGstreamerStreamsControl::setActive(int streamNumber, bool state) +{ + QMediaStreamsControl::StreamType type = m_session->streamType(streamNumber); + if (type == QMediaStreamsControl::UnknownStream) + return; + + if (state) + m_session->setActiveStream(type, streamNumber); + else { + //only one active stream of certain type is supported + if (m_session->activeStream(type) == streamNumber) + m_session->setActiveStream(type, -1); + } +} + diff --git a/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol_p.h b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol_p.h new file mode 100644 index 000000000..730dcba3a --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediaplayer/qgstreamerstreamscontrol_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 QGSTREAMERSTREAMSCONTROL_H +#define QGSTREAMERSTREAMSCONTROL_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 <qmediastreamscontrol.h> + +QT_BEGIN_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerStreamsControl : public QMediaStreamsControl +{ + Q_OBJECT +public: + QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent); + virtual ~QGstreamerStreamsControl(); + + int streamCount() override; + StreamType streamType(int streamNumber) override; + + QVariant metaData(int streamNumber, const QString &key) override; + + bool isActive(int streamNumber) override; + void setActive(int streamNumber, bool state) override; + +private: + QGstreamerPlayerSession *m_session = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QGSTREAMERSTREAMSCONTROL_H + diff --git a/src/multimedia/platform/opensles/opensles.pri b/src/multimedia/platform/opensles/opensles.pri index 845c2f680..c8f0eda4f 100644 --- a/src/multimedia/platform/opensles/opensles.pri +++ b/src/multimedia/platform/opensles/opensles.pri @@ -1,15 +1,15 @@ LIBS += -lOpenSLES HEADERS += \ - platform/opensles/qopenslesinterface_p.h \ - platform/opensles/qopenslesengine_p.h \ - platform/opensles/qopenslesdeviceinfo_p.h \ - platform/opensles/qopenslesaudioinput_p.h \ - platform/opensles/qopenslesaudiooutput_p.h + $$PWD/qopenslesinterface_p.h \ + $$PWD/qopenslesengine_p.h \ + $$PWD/qopenslesdeviceinfo_p.h \ + $$PWD/qopenslesaudioinput_p.h \ + $$PWD/qopenslesaudiooutput_p.h SOURCES += \ - platform/opensles/qopenslesinterface.cpp \ - platform/opensles/qopenslesengine.cpp \ - platform/opensles/qopenslesdeviceinfo.cpp \ - platform/opensles/qopenslesaudioinput.cpp \ - platform/opensles/qopenslesaudiooutput.cpp + $$PWD/qopenslesinterface.cpp \ + $$PWD/qopenslesengine.cpp \ + $$PWD/qopenslesdeviceinfo.cpp \ + $$PWD/qopenslesaudioinput.cpp \ + $$PWD/qopenslesaudiooutput.cpp diff --git a/src/multimedia/platform/platform.pri b/src/multimedia/platform/platform.pri index 5d2ab45f5..b1c0d5db5 100644 --- a/src/multimedia/platform/platform.pri +++ b/src/multimedia/platform/platform.pri @@ -2,8 +2,17 @@ qtConfig(gstreamer):include(gstreamer/gstreamer.pri) qtConfig(pulseaudio): include(pulseaudio/pulseaudio.pri) qtConfig(alsa): include(alsa/alsa.pri) -android: include(opensles/opensles.pri) -win32: include(wasapi/wasapi.pri) -darwin:!watchos: include(coreaudio/coreaudio.pri) +android { + include(android/android.pri) + include(opensles/opensles.pri) +} +win32 { + include(wasapi/wasapi.pri) + include(wmf/wmf.pri) +} +darwin:!watchos { + include(coreaudio/coreaudio.pri) + include(avfoundation/avfoundation.pri) +} qnx: include(qnx/qnx.pri) diff --git a/src/multimedia/platform/pulseaudio/pulseaudio.pri b/src/multimedia/platform/pulseaudio/pulseaudio.pri index c46054579..abf48caaa 100644 --- a/src/multimedia/platform/pulseaudio/pulseaudio.pri +++ b/src/multimedia/platform/pulseaudio/pulseaudio.pri @@ -1,15 +1,15 @@ QMAKE_USE_PRIVATE += pulseaudio -HEADERS += platform/pulseaudio/qaudiointerface_pulse_p.h \ - platform/pulseaudio/qaudiodeviceinfo_pulse_p.h \ - platform/pulseaudio/qaudiooutput_pulse_p.h \ - platform/pulseaudio/qaudioinput_pulse_p.h \ - platform/pulseaudio/qaudioengine_pulse_p.h \ - platform/pulseaudio/qpulsehelpers_p.h +HEADERS += $$PWD/qaudiointerface_pulse_p.h \ + $$PWD/qaudiodeviceinfo_pulse_p.h \ + $$PWD/qaudiooutput_pulse_p.h \ + $$PWD/qaudioinput_pulse_p.h \ + $$PWD/qaudioengine_pulse_p.h \ + $$PWD/qpuls_p.helpers_p.h -SOURCES += platform/pulseaudio/qaudiointerface_pulse.cpp \ - platform/pulseaudio/qaudiodeviceinfo_pulse.cpp \ - platform/pulseaudio/qaudiooutput_pulse.cpp \ - platform/pulseaudio/qaudioinput_pulse.cpp \ - platform/pulseaudio/qaudioengine_pulse.cpp \ - platform/pulseaudio/qpulsehelpers.cpp +SOURCES += $$PWD/qaudiointerface_pulse.cpp \ + $$PWD/qaudiodeviceinfo_pulse.cpp \ + $$PWD/qaudiooutput_pulse.cpp \ + $$PWD/qaudioinput_pulse.cpp \ + $$PWD/qaudioengine_pulse.cpp \ + $$PWD/qpuls_p.helpers.cpp diff --git a/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol.cpp new file mode 100644 index 000000000..2c0529bc4 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraaudioencodersettingscontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbCameraAudioEncoderSettingsControl::BbCameraAudioEncoderSettingsControl(BbCameraSession *session, QObject *parent) + : QAudioEncoderSettingsControl(parent) + , m_session(session) +{ +} + +QStringList BbCameraAudioEncoderSettingsControl::supportedAudioCodecs() const +{ + return QStringList() << QLatin1String("none") << QLatin1String("aac") << QLatin1String("raw"); +} + +QString BbCameraAudioEncoderSettingsControl::codecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("none")) + return tr("No compression"); + else if (codecName == QLatin1String("aac")) + return tr("AAC compression"); + else if (codecName == QLatin1String("raw")) + return tr("PCM uncompressed"); + + return QString(); +} + +QList<int> BbCameraAudioEncoderSettingsControl::supportedSampleRates(const QAudioEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + Q_UNUSED(continuous); + + // no API provided by BB10 yet + return QList<int>(); +} + +QAudioEncoderSettings BbCameraAudioEncoderSettingsControl::audioSettings() const +{ + return m_session->audioSettings(); +} + +void BbCameraAudioEncoderSettingsControl::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_session->setAudioSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol_p.h new file mode 100644 index 000000000..cdc384537 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraaudioencodersettingscontrol_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAAUDIOENCODERSETTINGSCONTROL_H +#define BBCAMERAAUDIOENCODERSETTINGSCONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraAudioEncoderSettingsControl : public QAudioEncoderSettingsControl +{ + Q_OBJECT +public: + explicit BbCameraAudioEncoderSettingsControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameracontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameracontrol.cpp new file mode 100644 index 000000000..ba298301e --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameracontrol.cpp @@ -0,0 +1,290 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameracontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbCameraControl::BbCameraControl(BbCameraSession *session, QObject *parent) + : QCameraControl(parent) + , m_session(session) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), this, SIGNAL(statusChanged(QCamera::Status))); + connect(m_session, SIGNAL(stateChanged(QCamera::State)), this, SIGNAL(stateChanged(QCamera::State))); + connect(m_session, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString))); + connect(m_session, SIGNAL(captureModeChanged(QCamera::CaptureModes)), this, SIGNAL(captureModeChanged(QCamera::CaptureModes))); + + connect(m_session, SIGNAL(cameraOpened()), SLOT(cameraOpened())); + connect(m_session, SIGNAL(focusStatusChanged(int)), SLOT(focusStatusChanged(int))); +} + +QCamera::State BbCameraControl::state() const +{ + return m_session->state(); +} + +void BbCameraControl::setState(QCamera::State state) +{ + m_session->setState(state); +} + +QCamera::CaptureModes BbCameraControl::captureMode() const +{ + return m_session->captureMode(); +} + +void BbCameraControl::setCaptureMode(QCamera::CaptureModes mode) +{ + m_session->setCaptureMode(mode); +} + +QCamera::Status BbCameraControl::status() const +{ + return m_session->status(); +} + +bool BbCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + return m_session->isCaptureModeSupported(mode); +} + +bool BbCameraControl::canChangeProperty(PropertyChangeType /* changeType */, QCamera::Status /* status */) const +{ + return false; +} + +QCamera::LockTypes BbCameraControl::supportedLocks() const +{ + return (QCamera::LockFocus | QCamera::LockExposure | QCamera::LockWhiteBalance); +} + +QCamera::LockStatus BbCameraControl::lockStatus(QCamera::LockType lock) const +{ + if (!m_supportedLockTypes.testFlag(lock) || (m_session->handle() == CAMERA_HANDLE_INVALID)) + return QCamera::Locked; + + switch (lock) { + case QCamera::LockExposure: + return m_exposureLockStatus; + case QCamera::LockWhiteBalance: + return m_whiteBalanceLockStatus; + case QCamera::LockFocus: + return m_focusLockStatus; + default: + return QCamera::Locked; + } +} + +void BbCameraControl::searchAndLock(QCamera::LockTypes locks) +{ + if (m_session->handle() == CAMERA_HANDLE_INVALID) + return; + + // filter out unsupported locks + locks &= m_supportedLockTypes; + + m_currentLockTypes |= locks; + + uint32_t lockModes = CAMERA_3A_NONE; + + switch (m_locksApplyMode) { + case IndependentMode: + if (m_currentLockTypes & QCamera::LockExposure) + lockModes |= CAMERA_3A_AUTOEXPOSURE; + if (m_currentLockTypes & QCamera::LockWhiteBalance) + lockModes |= CAMERA_3A_AUTOWHITEBALANCE; + if (m_currentLockTypes & QCamera::LockFocus) + lockModes |= CAMERA_3A_AUTOFOCUS; + break; + case FocusExposureBoundMode: + if ((m_currentLockTypes & QCamera::LockExposure) || (m_currentLockTypes & QCamera::LockFocus)) + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS); + break; + case AllBoundMode: + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOWHITEBALANCE); + break; + case FocusOnlyMode: + lockModes = CAMERA_3A_AUTOFOCUS; + break; + } + + const camera_error_t result = camera_set_3a_lock(m_session->handle(), lockModes); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to set lock modes:" << result; + } else { + if (lockModes & CAMERA_3A_AUTOFOCUS) { + // handled by focusStatusChanged() + } + + if (lockModes & CAMERA_3A_AUTOEXPOSURE) { + m_exposureLockStatus = QCamera::Locked; + emit lockStatusChanged(QCamera::LockExposure, QCamera::Locked, QCamera::LockAcquired); + } + + if (lockModes & CAMERA_3A_AUTOWHITEBALANCE) { + m_whiteBalanceLockStatus = QCamera::Locked; + emit lockStatusChanged(QCamera::LockWhiteBalance, QCamera::Locked, QCamera::LockAcquired); + } + } +} + +void BbCameraControl::unlock(QCamera::LockTypes locks) +{ + // filter out unsupported locks + locks &= m_supportedLockTypes; + + m_currentLockTypes &= ~locks; + + uint32_t lockModes = CAMERA_3A_NONE; + + switch (m_locksApplyMode) { + case IndependentMode: + if (m_currentLockTypes & QCamera::LockExposure) + lockModes |= CAMERA_3A_AUTOEXPOSURE; + if (m_currentLockTypes & QCamera::LockWhiteBalance) + lockModes |= CAMERA_3A_AUTOWHITEBALANCE; + if (m_currentLockTypes & QCamera::LockFocus) + lockModes |= CAMERA_3A_AUTOFOCUS; + break; + case FocusExposureBoundMode: + if ((m_currentLockTypes & QCamera::LockExposure) || (m_currentLockTypes & QCamera::LockFocus)) + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS); + break; + case AllBoundMode: + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOWHITEBALANCE); + break; + case FocusOnlyMode: + lockModes = CAMERA_3A_AUTOFOCUS; + break; + } + + const camera_error_t result = camera_set_3a_lock(m_session->handle(), lockModes); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to set lock modes:" << result; + } else { + if (locks.testFlag(QCamera::LockFocus)) { + // handled by focusStatusChanged() + } + + if (locks.testFlag(QCamera::LockExposure)) { + m_exposureLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockExposure, QCamera::Unlocked, QCamera::UserRequest); + } + + if (locks.testFlag(QCamera::LockWhiteBalance)) { + m_whiteBalanceLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockWhiteBalance, QCamera::Unlocked, QCamera::UserRequest); + } + } +} + +void BbCameraControl::cameraOpened() +{ + // retrieve information about lock apply modes + int supported = 0; + uint32_t modes[20]; + + const camera_error_t result = camera_get_3a_lock_modes(m_session->handle(), 20, &supported, modes); + + if (result == CAMERA_EOK) { + // see API documentation of camera_get_3a_lock_modes for explanation of case discrimination below + if (supported == 4) { + m_locksApplyMode = IndependentMode; + } else if (supported == 3) { + m_locksApplyMode = FocusExposureBoundMode; + } else if (supported == 2) { + if (modes[0] == (CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOWHITEBALANCE)) + m_locksApplyMode = AllBoundMode; + else + m_locksApplyMode = FocusOnlyMode; + } + } + + // retrieve information about supported lock types + m_supportedLockTypes = QCamera::NoLock; + + if (camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS)) + m_supportedLockTypes |= QCamera::LockFocus; + + if (camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOEXPOSURE)) + m_supportedLockTypes |= QCamera::LockExposure; + + if (camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOWHITEBALANCE)) + m_supportedLockTypes |= QCamera::LockWhiteBalance; + + m_focusLockStatus = QCamera::Unlocked; + m_exposureLockStatus = QCamera::Unlocked; + m_whiteBalanceLockStatus = QCamera::Unlocked; +} + +void BbCameraControl::focusStatusChanged(int value) +{ + const camera_focusstate_t focusState = static_cast<camera_focusstate_t>(value); + + switch (focusState) { + case CAMERA_FOCUSSTATE_NONE: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::UserRequest); + break; + case CAMERA_FOCUSSTATE_WAITING: + case CAMERA_FOCUSSTATE_SEARCHING: + m_focusLockStatus = QCamera::Searching; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Searching, QCamera::UserRequest); + break; + case CAMERA_FOCUSSTATE_FAILED: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::LockFailed); + break; + case CAMERA_FOCUSSTATE_LOCKED: + m_focusLockStatus = QCamera::Locked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Locked, QCamera::LockAcquired); + break; + case CAMERA_FOCUSSTATE_SCENECHANGE: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::LockTemporaryLost); + break; + default: + break; + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameracontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameracontrol_p.h new file mode 100644 index 000000000..7557300cd --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameracontrol_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERACONTROL_H +#define BBCAMERACONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraControl : public QCameraControl +{ + Q_OBJECT +public: + explicit BbCameraControl(BbCameraSession *session, QObject *parent = 0); + + 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) override; + bool isCaptureModeSupported(QCamera::CaptureModes mode) const override; + + bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const override; + + enum LocksApplyMode + { + IndependentMode, + FocusExposureBoundMode, + AllBoundMode, + FocusOnlyMode + }; + + 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; + +private Q_SLOTS: + void cameraOpened(); + void focusStatusChanged(int value); + +private: + BbCameraSession *m_session; + + LocksApplyMode m_locksApplyMode = IndependentMode; + QCamera::LockStatus m_focusLockStatus = QCamera::Unlocked; + QCamera::LockStatus m_exposureLockStatus = QCamera::Unlocked; + QCamera::LockStatus m_whiteBalanceLockStatus = QCamera::Unlocked; + QCamera::LockTypes m_currentLockTypes = QCamera::NoLock; + QCamera::LockTypes m_supportedLockTypes = QCamera::NoLock; + +private: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol.cpp new file mode 100644 index 000000000..6dbde9556 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol.cpp @@ -0,0 +1,287 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraexposurecontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +BbCameraExposureControl::BbCameraExposureControl(BbCameraSession *session, QObject *parent) + : QCameraExposureControl(parent) + , m_session(session) + , m_requestedExposureMode(QCameraExposure::ExposureAuto) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), this, SLOT(statusChanged(QCamera::Status))); +} + +bool BbCameraExposureControl::isParameterSupported(ExposureParameter parameter) const +{ + switch (parameter) { + case QCameraExposureControl::ISO: + return false; + case QCameraExposureControl::Aperture: + return false; + case QCameraExposureControl::ShutterSpeed: + return false; + case QCameraExposureControl::ExposureCompensation: + return false; + case QCameraExposureControl::FlashPower: + return false; + case QCameraExposureControl::FlashCompensation: + return false; + case QCameraExposureControl::TorchPower: + return false; + case QCameraExposureControl::ExposureMode: + return true; + case QCameraExposureControl::MeteringMode: + return false; + default: + return false; + } +} + +QVariantList BbCameraExposureControl::supportedParameterRange(ExposureParameter parameter, bool *continuous) const +{ + if (parameter != QCameraExposureControl::ExposureMode) // no other parameter supported by BB10 API at the moment + return QVariantList(); + + if (m_session->status() != QCamera::ActiveStatus) // we can query supported exposure modes only with active viewfinder + return QVariantList(); + + if (continuous) + *continuous = false; + + int supported = 0; + camera_scenemode_t modes[20]; + const camera_error_t result = camera_get_scene_modes(m_session->handle(), 20, &supported, modes); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve supported scene modes:" << result; + return QVariantList(); + } + + QVariantList exposureModes; + for (int i = 0; i < supported; ++i) { + switch (modes[i]) { + case CAMERA_SCENE_AUTO: + exposureModes << QVariant::fromValue(QCameraExposure::ExposureAuto); + break; + case CAMERA_SCENE_SPORTS: + exposureModes << QVariant::fromValue(QCameraExposure::ExposureSports); + break; + case CAMERA_SCENE_CLOSEUP: + exposureModes << QVariant::fromValue(QCameraExposure::ExposurePortrait); + break; + case CAMERA_SCENE_ACTION: + exposureModes << QVariant::fromValue(QCameraExposure::ExposureSports); + break; + case CAMERA_SCENE_BEACHANDSNOW: + exposureModes << QVariant::fromValue(QCameraExposure::ExposureBeach) << QVariant::fromValue(QCameraExposure::ExposureSnow); + break; + case CAMERA_SCENE_NIGHT: + exposureModes << QVariant::fromValue(QCameraExposure::ExposureNight); + break; + default: break; + } + } + + return exposureModes; +} + +QVariant BbCameraExposureControl::requestedValue(ExposureParameter parameter) const +{ + if (parameter != QCameraExposureControl::ExposureMode) // no other parameter supported by BB10 API at the moment + return QVariant(); + + return QVariant::fromValue(m_requestedExposureMode); +} + +QVariant BbCameraExposureControl::actualValue(ExposureParameter parameter) const +{ + if (parameter != QCameraExposureControl::ExposureMode) // no other parameter supported by BB10 API at the moment + return QVariantList(); + + if (m_session->status() != QCamera::ActiveStatus) // we can query actual scene modes only with active viewfinder + return QVariantList(); + + camera_scenemode_t sceneMode = CAMERA_SCENE_DEFAULT; + const camera_error_t result = camera_get_scene_mode(m_session->handle(), &sceneMode); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve scene mode:" << result; + return QVariant(); + } + + switch (sceneMode) { + case CAMERA_SCENE_AUTO: + return QVariant::fromValue(QCameraExposure::ExposureAuto); + case CAMERA_SCENE_SPORTS: + return QVariant::fromValue(QCameraExposure::ExposureSports); + case CAMERA_SCENE_CLOSEUP: + return QVariant::fromValue(QCameraExposure::ExposurePortrait); + case CAMERA_SCENE_ACTION: + return QVariant::fromValue(QCameraExposure::ExposureSports); + case CAMERA_SCENE_BEACHANDSNOW: + return (m_requestedExposureMode == QCameraExposure::ExposureBeach ? QVariant::fromValue(QCameraExposure::ExposureBeach) + : QVariant::fromValue(QCameraExposure::ExposureSnow)); + case CAMERA_SCENE_NIGHT: + return QVariant::fromValue(QCameraExposure::ExposureNight); + default: + break; + } + + return QVariant(); +} + +bool BbCameraExposureControl::setValue(ExposureParameter parameter, const QVariant& value) +{ + if (parameter != QCameraExposureControl::ExposureMode) // no other parameter supported by BB10 API at the moment + return false; + + if (m_session->status() != QCamera::ActiveStatus) // we can set actual scene modes only with active viewfinder + return false; + + camera_scenemode_t sceneMode = CAMERA_SCENE_DEFAULT; + + if (value.isValid()) { + m_requestedExposureMode = value.value<QCameraExposure::ExposureMode>(); + emit requestedValueChanged(QCameraExposureControl::ExposureMode); + + switch (m_requestedExposureMode) { + case QCameraExposure::ExposureAuto: + sceneMode = CAMERA_SCENE_AUTO; + break; + case QCameraExposure::ExposureSports: + sceneMode = CAMERA_SCENE_SPORTS; + break; + case QCameraExposure::ExposurePortrait: + sceneMode = CAMERA_SCENE_CLOSEUP; + break; + case QCameraExposure::ExposureBeach: + sceneMode = CAMERA_SCENE_BEACHANDSNOW; + break; + case QCameraExposure::ExposureSnow: + sceneMode = CAMERA_SCENE_BEACHANDSNOW; + break; + case QCameraExposure::ExposureNight: + sceneMode = CAMERA_SCENE_NIGHT; + break; + default: + sceneMode = CAMERA_SCENE_DEFAULT; + break; + } + } + + const camera_error_t result = camera_set_scene_mode(m_session->handle(), sceneMode); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to set scene mode:" << result; + return false; + } + + emit actualValueChanged(QCameraExposureControl::ExposureMode); + + return true; +} + +void BbCameraExposureControl::statusChanged(QCamera::Status status) +{ + if (status == QCamera::ActiveStatus || status == QCamera::LoadedStatus) + emit parameterRangeChanged(QCameraExposureControl::ExposureMode); +} + +QCameraExposure::FlashModes BbCameraExposureControl::flashMode() const +{ + return m_flashMode; +} + +void BbCameraExposureControl::setFlashMode(QCameraExposure::FlashModes mode) +{ + if (m_flashMode == mode) + return; + + if (m_session->status() != QCamera::ActiveStatus) // can only be changed when viewfinder is active + return; + + if (m_flashMode == QCameraExposure::FlashVideoLight) { + const camera_error_t result = camera_config_videolight(m_session->handle(), CAMERA_VIDEOLIGHT_OFF); + if (result != CAMERA_EOK) + qWarning() << "Unable to switch off video light:" << result; + } + + m_flashMode = mode; + + if (m_flashMode == QCameraExposure::FlashVideoLight) { + const camera_error_t result = camera_config_videolight(m_session->handle(), CAMERA_VIDEOLIGHT_ON); + if (result != CAMERA_EOK) + qWarning() << "Unable to switch on video light:" << result; + } else { + camera_flashmode_t flashMode = CAMERA_FLASH_AUTO; + + if (m_flashMode.testFlag(QCameraExposure::FlashAuto)) flashMode = CAMERA_FLASH_AUTO; + else if (mode.testFlag(QCameraExposure::FlashOff)) flashMode = CAMERA_FLASH_OFF; + else if (mode.testFlag(QCameraExposure::FlashOn)) flashMode = CAMERA_FLASH_ON; + + const camera_error_t result = camera_config_flash(m_session->handle(), flashMode); + if (result != CAMERA_EOK) + qWarning() << "Unable to configure flash:" << result; + } +} + +bool BbCameraExposureControl::isFlashModeSupported(QCameraExposure::FlashModes mode) const +{ + bool supportsVideoLight = false; + if (m_session->handle() != CAMERA_HANDLE_INVALID) { + supportsVideoLight = camera_has_feature(m_session->handle(), CAMERA_FEATURE_VIDEOLIGHT); + } + + return (mode == QCameraExposure::FlashOff || + mode == QCameraExposure::FlashOn || + mode == QCameraExposure::FlashAuto || + ((mode == QCameraExposure::FlashVideoLight) && supportsVideoLight)); +} + +bool BbCameraExposureControl::isFlashReady() const +{ + //TODO: check for flash charge-level here?!? + return true; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol_p.h new file mode 100644 index 000000000..56ea61b7f --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraexposurecontrol_p.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAEXPOSURECONTROL_H +#define BBCAMERAEXPOSURECONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraExposureControl : public QCameraExposureControl +{ + Q_OBJECT +public: + explicit BbCameraExposureControl(BbCameraSession *session, QObject *parent = 0); + + 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 statusChanged(QCamera::Status status); + +private: + BbCameraSession *m_session; + QCameraExposure::ExposureMode m_requestedExposureMode; + + QCameraExposure::FlashModes m_flashMode = QCameraExposure::FlashAuto; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol.cpp b/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol.cpp new file mode 100644 index 000000000..25f61190c --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol.cpp @@ -0,0 +1,428 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcamerafocuscontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +BbCameraFocusControl::BbCameraFocusControl(BbCameraSession *session, QObject *parent) + : QCameraFocusControl(parent) + , m_session(session) + , m_focusMode(QCameraFocus::FocusModes()) + , m_focusPointMode(QCameraFocus::FocusPointAuto) + , m_customFocusPoint(QPointF(0, 0)) +{ + connect(m_session, SIGNAL(statusChanged(QCamera::Status)), this, SLOT(statusChanged(QCamera::Status))); +} + +QCameraFocus::FocusModes BbCameraFocusControl::focusMode() const +{ + camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF; + + const camera_error_t result = camera_get_focus_mode(m_session->handle(), &focusMode); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve focus mode from camera:" << result; + return QCameraFocus::FocusModes(); + } + + switch (focusMode) { + case CAMERA_FOCUSMODE_EDOF: + return QCameraFocus::HyperfocalFocus; + case CAMERA_FOCUSMODE_MANUAL: + return QCameraFocus::ManualFocus; + case CAMERA_FOCUSMODE_AUTO: + return QCameraFocus::AutoFocus; + case CAMERA_FOCUSMODE_MACRO: + return QCameraFocus::MacroFocus; + case CAMERA_FOCUSMODE_CONTINUOUS_AUTO: + return QCameraFocus::ContinuousFocus; + case CAMERA_FOCUSMODE_CONTINUOUS_MACRO: // fall through + case CAMERA_FOCUSMODE_OFF: // fall through + default: + return QCameraFocus::FocusModes(); + } +} + +void BbCameraFocusControl::setFocusMode(QCameraFocus::FocusModes mode) +{ + if (m_focusMode == mode) + return; + + camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF; + + if (mode == QCameraFocus::HyperfocalFocus) + focusMode = CAMERA_FOCUSMODE_EDOF; + else if (mode == QCameraFocus::ManualFocus) + focusMode = CAMERA_FOCUSMODE_MANUAL; + else if (mode == QCameraFocus::AutoFocus) + focusMode = CAMERA_FOCUSMODE_AUTO; + else if (mode == QCameraFocus::MacroFocus) + focusMode = CAMERA_FOCUSMODE_MACRO; + else if (mode == QCameraFocus::ContinuousFocus) + focusMode = CAMERA_FOCUSMODE_CONTINUOUS_AUTO; + + const camera_error_t result = camera_set_focus_mode(m_session->handle(), focusMode); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to set focus mode:" << result; + return; + } + + m_focusMode = mode; + emit focusModeChanged(m_focusMode); +} + +bool BbCameraFocusControl::isFocusModeSupported(QCameraFocus::FocusModes mode) const +{ + if (m_session->state() == QCamera::UnloadedState) + return false; + + if (mode == QCameraFocus::HyperfocalFocus) + return false; //TODO how to check? + else if (mode == QCameraFocus::ManualFocus) + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_MANUALFOCUS); + else if (mode == QCameraFocus::AutoFocus) + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS); + else if (mode == QCameraFocus::MacroFocus) + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_MACROFOCUS); + else if (mode == QCameraFocus::ContinuousFocus) + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS); + + return false; +} + +QCameraFocus::FocusPointMode BbCameraFocusControl::focusPointMode() const +{ + return m_focusPointMode; +} + +void BbCameraFocusControl::setFocusPointMode(QCameraFocus::FocusPointMode mode) +{ + if (m_session->status() != QCamera::ActiveStatus) + return; + + if (m_focusPointMode == mode) + return; + + m_focusPointMode = mode; + emit focusPointModeChanged(m_focusPointMode); + + if (m_focusPointMode == QCameraFocus::FocusPointAuto) { + //TODO: is this correct? + const camera_error_t result = camera_set_focus_regions(m_session->handle(), 0, 0); + if (result != CAMERA_EOK) { + qWarning() << "Unable to set focus region:" << result; + return; + } + + emit focusZonesChanged(); + } else if (m_focusPointMode == QCameraFocus::FocusPointCenter) { + // get the size of the viewfinder + int viewfinderWidth = 0; + int viewfinderHeight = 0; + + if (!retrieveViewfinderSize(&viewfinderWidth, &viewfinderHeight)) + return; + + // define a 40x40 pixel focus region in the center of the viewfinder + camera_region_t focusRegion; + focusRegion.left = (viewfinderWidth / 2) - 20; + focusRegion.top = (viewfinderHeight / 2) - 20; + focusRegion.width = 40; + focusRegion.height = 40; + + camera_error_t result = camera_set_focus_regions(m_session->handle(), 1, &focusRegion); + if (result != CAMERA_EOK) { + qWarning() << "Unable to set focus region:" << result; + return; + } + + // re-set focus mode to apply focus region changes + camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF; + result = camera_get_focus_mode(m_session->handle(), &focusMode); + camera_set_focus_mode(m_session->handle(), focusMode); + + emit focusZonesChanged(); + + } else if (m_focusPointMode == QCameraFocus::FocusPointFaceDetection) { + //TODO: implement later + } else if (m_focusPointMode == QCameraFocus::FocusPointCustom) { + updateCustomFocusRegion(); + } +} + +bool BbCameraFocusControl::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const +{ + if (m_session->state() == QCamera::UnloadedState) + return false; + + if (mode == QCameraFocus::FocusPointAuto) { + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_AUTOFOCUS); + } else if (mode == QCameraFocus::FocusPointCenter) { + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_REGIONFOCUS); + } else if (mode == QCameraFocus::FocusPointFaceDetection) { + return false; //TODO: implement via custom region in combination with face detection in viewfinder + } else if (mode == QCameraFocus::FocusPointCustom) { + return camera_has_feature(m_session->handle(), CAMERA_FEATURE_REGIONFOCUS); + } + + return false; +} + +QPointF BbCameraFocusControl::customFocusPoint() const +{ + return m_customFocusPoint; +} + +void BbCameraFocusControl::setCustomFocusPoint(const QPointF &point) +{ + if (m_customFocusPoint == point) + return; + + m_customFocusPoint = point; + emit customFocusPointChanged(m_customFocusPoint); + + updateCustomFocusRegion(); +} + +QCameraFocusZoneList BbCameraFocusControl::focusZones() const +{ + if (m_session->state() == QCamera::UnloadedState) + return QCameraFocusZoneList(); + + camera_region_t regions[20]; + int supported = 0; + int asked = 0; + camera_error_t result = camera_get_focus_regions(m_session->handle(), 20, &supported, &asked, regions); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve focus regions:" << result; + return QCameraFocusZoneList(); + } + + // retrieve width and height of viewfinder + int viewfinderWidth = 0; + int viewfinderHeight = 0; + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), + CAMERA_IMGPROP_WIDTH, &viewfinderWidth, + CAMERA_IMGPROP_HEIGHT, &viewfinderHeight); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), + CAMERA_IMGPROP_WIDTH, &viewfinderWidth, + CAMERA_IMGPROP_HEIGHT, &viewfinderHeight); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve viewfinder size:" << result; + return QCameraFocusZoneList(); + } + + QCameraFocusZoneList list; + for (int i = 0; i < asked; ++i) { + const int x = regions[i].left; + const int y = regions[i].top; + const int width = regions[i].width; + const int height = regions[i].height; + + QRectF rect(static_cast<float>(x)/static_cast<float>(viewfinderWidth), + static_cast<float>(y)/static_cast<float>(viewfinderHeight), + static_cast<float>(width)/static_cast<float>(viewfinderWidth), + static_cast<float>(height)/static_cast<float>(viewfinderHeight)); + + list << QCameraFocusZone(rect, QCameraFocusZone::Focused); //TODO: how to know if a zone is unused/selected/focused?!? + } + + return list; +} + +void BbCameraFocusControl::updateCustomFocusRegion() +{ + // get the size of the viewfinder + int viewfinderWidth = 0; + int viewfinderHeight = 0; + + if (!retrieveViewfinderSize(&viewfinderWidth, &viewfinderHeight)) + return; + + // define a 40x40 pixel focus region around the custom focus point + camera_region_t focusRegion; + focusRegion.left = qMax(0, static_cast<int>(m_customFocusPoint.x() * viewfinderWidth) - 20); + focusRegion.top = qMax(0, static_cast<int>(m_customFocusPoint.y() * viewfinderHeight) - 20); + focusRegion.width = 40; + focusRegion.height = 40; + + camera_error_t result = camera_set_focus_regions(m_session->handle(), 1, &focusRegion); + if (result != CAMERA_EOK) { + qWarning() << "Unable to set focus region:" << result; + return; + } + + // re-set focus mode to apply focus region changes + camera_focusmode_t focusMode = CAMERA_FOCUSMODE_OFF; + result = camera_get_focus_mode(m_session->handle(), &focusMode); + camera_set_focus_mode(m_session->handle(), focusMode); + + emit focusZonesChanged(); +} + +bool BbCameraFocusControl::retrieveViewfinderSize(int *width, int *height) +{ + if (!width || !height) + return false; + + camera_error_t result = CAMERA_EOK; + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), + CAMERA_IMGPROP_WIDTH, width, + CAMERA_IMGPROP_HEIGHT, height); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), + CAMERA_IMGPROP_WIDTH, width, + CAMERA_IMGPROP_HEIGHT, height); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve viewfinder size:" << result; + return false; + } + + return true; +} + + +qreal BbCameraFocusControl::maximumOpticalZoom() const +{ + //TODO: optical zoom support not available in BB10 API yet + return 1.0; +} + +qreal BbCameraFocusControl::maximumDigitalZoom() const +{ + return m_maximumZoomFactor; +} + +qreal BbCameraFocusControl::requestedOpticalZoom() const +{ + //TODO: optical zoom support not available in BB10 API yet + return 1.0; +} + +qreal BbCameraFocusControl::requestedDigitalZoom() const +{ + return currentDigitalZoom(); +} + +qreal BbCameraFocusControl::currentOpticalZoom() const +{ + //TODO: optical zoom support not available in BB10 API yet + return 1.0; +} + +qreal BbCameraFocusControl::currentDigitalZoom() const +{ + if (m_session->status() != QCamera::ActiveStatus) + return 1.0; + + unsigned int zoomFactor = 0; + camera_error_t result = CAMERA_EOK; + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), CAMERA_IMGPROP_ZOOMFACTOR, &zoomFactor); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), CAMERA_IMGPROP_ZOOMFACTOR, &zoomFactor); + + if (result != CAMERA_EOK) + return 1.0; + + return zoomFactor; +} + +void BbCameraFocusControl::zoomTo(qreal optical, qreal digital) +{ + Q_UNUSED(optical); + + if (m_session->status() != QCamera::ActiveStatus) + return; + + const qreal actualZoom = qBound(m_minimumZoomFactor, digital, m_maximumZoomFactor); + + const camera_error_t result = camera_set_zoom(m_session->handle(), actualZoom, false); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to change zoom factor:" << result; + return; + } + + if (m_requestedZoomFactor != digital) { + m_requestedZoomFactor = digital; + emit requestedDigitalZoomChanged(m_requestedZoomFactor); + } + + emit currentDigitalZoomChanged(actualZoom); +} + +void BbCameraFocusControl::statusChanged(QCamera::Status status) +{ + if (status == QCamera::ActiveStatus) { + // retrieve information about zoom limits + unsigned int maximumZoomLimit = 0; + unsigned int minimumZoomLimit = 0; + bool smoothZoom = false; + + const camera_error_t result = camera_get_zoom_limits(m_session->handle(), &maximumZoomLimit, &minimumZoomLimit, &smoothZoom); + if (result == CAMERA_EOK) { + const qreal oldMaximumZoomFactor = m_maximumZoomFactor; + m_maximumZoomFactor = maximumZoomLimit; + + if (oldMaximumZoomFactor != m_maximumZoomFactor) + emit maximumDigitalZoomChanged(m_maximumZoomFactor); + + m_minimumZoomFactor = minimumZoomLimit; + m_supportsSmoothZoom = smoothZoom; + } else { + m_maximumZoomFactor = 1.0; + m_minimumZoomFactor = 1.0; + m_supportsSmoothZoom = false; + } + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol_p.h b/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol_p.h new file mode 100644 index 000000000..e91e032c5 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcamerafocuscontrol_p.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAFOCUSCONTROL_H +#define BBCAMERAFOCUSCONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraFocusControl : public QCameraFocusControl +{ + Q_OBJECT +public: + explicit BbCameraFocusControl(BbCameraSession *session, QObject *parent = 0); + + 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 statusChanged(QCamera::Status status); + +private: + void updateCustomFocusRegion(); + bool retrieveViewfinderSize(int *width, int *height); + + BbCameraSession *m_session; + + QCameraFocus::FocusModes m_focusMode; + QCameraFocus::FocusPointMode m_focusPointMode; + QPointF m_customFocusPoint; + + qreal m_minimumZoomFactor; + qreal m_maximumZoomFactor; + bool m_supportsSmoothZoom; + qreal m_requestedZoomFactor; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol.cpp new file mode 100644 index 000000000..25f0468ef --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraimagecapturecontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbCameraImageCaptureControl::BbCameraImageCaptureControl(BbCameraSession *session, QObject *parent) + : QCameraImageCaptureControl(parent) + , 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 BbCameraImageCaptureControl::isReadyForCapture() const +{ + return m_session->isReadyForCapture(); +} + +QCameraImageCapture::DriveMode BbCameraImageCaptureControl::driveMode() const +{ + return m_session->driveMode(); +} + +void BbCameraImageCaptureControl::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_session->setDriveMode(mode); +} + +int BbCameraImageCaptureControl::capture(const QString &fileName) +{ + return m_session->capture(fileName); +} + +void BbCameraImageCaptureControl::cancelCapture() +{ + m_session->cancelCapture(); +} + +QCameraImageCapture::CaptureDestinations BbCameraImageCaptureControl::captureDestination() const +{ + return m_session->captureDestination(); +} + +void BbCameraImageCaptureControl::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + m_session->setCaptureDestination(destination); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol_p.h new file mode 100644 index 000000000..f36145cf6 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraimagecapturecontrol_p.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAIMAGECAPTURECONTROL_H +#define BBCAMERAIMAGECAPTURECONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraImageCaptureControl : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + explicit BbCameraImageCaptureControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol.cpp new file mode 100644 index 000000000..a5787bd18 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraimageprocessingcontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +BbCameraImageProcessingControl::BbCameraImageProcessingControl(BbCameraSession *session, QObject *parent) + : QCameraImageProcessingControl(parent) + , m_session(session) +{ +} + +bool BbCameraImageProcessingControl::isParameterSupported(ProcessingParameter parameter) const +{ + return (parameter == QCameraImageProcessingControl::WhiteBalancePreset); +} + +bool BbCameraImageProcessingControl::isParameterValueSupported(ProcessingParameter parameter, const QVariant &value) const +{ + if (parameter != QCameraImageProcessingControl::WhiteBalancePreset) + return false; + + if (m_session->handle() == CAMERA_HANDLE_INVALID) + return false; + + int supported = 0; + camera_whitebalancemode_t modes[20]; + const camera_error_t result = camera_get_whitebalance_modes(m_session->handle(), 20, &supported, modes); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve supported whitebalance modes:" << result; + return false; + } + + QSet<QCameraImageProcessing::WhiteBalanceMode> supportedModes; + for (int i = 0; i < supported; ++i) { + switch (modes[i]) { + case CAMERA_WHITEBALANCEMODE_AUTO: + supportedModes.insert(QCameraImageProcessing::WhiteBalanceAuto); + break; + case CAMERA_WHITEBALANCEMODE_MANUAL: + supportedModes.insert(QCameraImageProcessing::WhiteBalanceManual); + break; + default: + break; + } + } + + return supportedModes.contains(value.value<QCameraImageProcessing::WhiteBalanceMode>()); +} + +QVariant BbCameraImageProcessingControl::parameter(ProcessingParameter parameter) const +{ + if (parameter != QCameraImageProcessingControl::WhiteBalancePreset) + return QVariant(); + + if (m_session->handle() == CAMERA_HANDLE_INVALID) + return QVariant(); + + camera_whitebalancemode_t mode; + const camera_error_t result = camera_get_whitebalance_mode(m_session->handle(), &mode); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve current whitebalance mode:" << result; + return QVariant(); + } + + switch (mode) { + case CAMERA_WHITEBALANCEMODE_AUTO: + return QVariant::fromValue(QCameraImageProcessing::WhiteBalanceAuto); + case CAMERA_WHITEBALANCEMODE_MANUAL: + return QVariant::fromValue(QCameraImageProcessing::WhiteBalanceManual); + default: + return QVariant(); + } +} + +void BbCameraImageProcessingControl::setParameter(ProcessingParameter parameter, const QVariant &value) +{ + if (parameter != QCameraImageProcessingControl::WhiteBalancePreset) + return; + + if (m_session->handle() == CAMERA_HANDLE_INVALID) + return; + + camera_whitebalancemode_t mode = CAMERA_WHITEBALANCEMODE_DEFAULT; + switch (value.value<QCameraImageProcessing::WhiteBalanceMode>()) { + case QCameraImageProcessing::WhiteBalanceAuto: + mode = CAMERA_WHITEBALANCEMODE_AUTO; + break; + case QCameraImageProcessing::WhiteBalanceManual: + mode = CAMERA_WHITEBALANCEMODE_MANUAL; + break; + default: + break; + } + + const camera_error_t result = camera_set_whitebalance_mode(m_session->handle(), mode); + + if (result != CAMERA_EOK) + qWarning() << "Unable to set whitebalance mode:" << result; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol_p.h new file mode 100644 index 000000000..6a174ebad --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraimageprocessingcontrol_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAIMAGEPROCESSINGCONTROL_H +#define BBCAMERAIMAGEPROCESSINGCONTROL_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.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraImageProcessingControl : public QCameraImageProcessingControl +{ + Q_OBJECT +public: + explicit BbCameraImageProcessingControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol.cpp new file mode 100644 index 000000000..e127f5d70 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol.cpp @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameramediarecordercontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> +#include <QUrl> + +#include <audio/audio_manager_device.h> +#include <audio/audio_manager_volume.h> + +QT_BEGIN_NAMESPACE + +static audio_manager_device_t currentAudioInputDevice() +{ + audio_manager_device_t device = AUDIO_DEVICE_HEADSET; + + const int result = audio_manager_get_default_input_device(&device); + if (result != EOK) { + qWarning() << "Unable to retrieve default audio input device:" << result; + return AUDIO_DEVICE_HEADSET; + } + + return device; +} + +BbCameraMediaRecorderControl::BbCameraMediaRecorderControl(BbCameraSession *session, QObject *parent) + : QMediaRecorderControl(parent) + , m_session(session) +{ + connect(m_session, SIGNAL(videoStateChanged(QMediaRecorder::State)), this, SIGNAL(stateChanged(QMediaRecorder::State))); + connect(m_session, SIGNAL(videoStatusChanged(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(videoError(int,QString)), this, SIGNAL(error(int,QString))); +} + +QUrl BbCameraMediaRecorderControl::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool BbCameraMediaRecorderControl::setOutputLocation(const QUrl &location) +{ + return m_session->setOutputLocation(location); +} + +QMediaRecorder::State BbCameraMediaRecorderControl::state() const +{ + return m_session->videoState(); +} + +QMediaRecorder::Status BbCameraMediaRecorderControl::status() const +{ + return m_session->videoStatus(); +} + +qint64 BbCameraMediaRecorderControl::duration() const +{ + return m_session->duration(); +} + +bool BbCameraMediaRecorderControl::isMuted() const +{ + bool muted = false; + + const int result = audio_manager_get_input_mute(currentAudioInputDevice(), &muted); + if (result != EOK) { + emit const_cast<BbCameraMediaRecorderControl*>(this)->error(QMediaRecorder::ResourceError, tr("Unable to retrieve mute status")); + return false; + } + + return muted; +} + +qreal BbCameraMediaRecorderControl::volume() const +{ + double level = 0.0; + + const int result = audio_manager_get_input_level(currentAudioInputDevice(), &level); + if (result != EOK) { + emit const_cast<BbCameraMediaRecorderControl*>(this)->error(QMediaRecorder::ResourceError, tr("Unable to retrieve audio input volume")); + return 0.0; + } + + return (level / 100); +} + +void BbCameraMediaRecorderControl::applySettings() +{ + m_session->applyVideoSettings(); +} + +void BbCameraMediaRecorderControl::setState(QMediaRecorder::State state) +{ + m_session->setVideoState(state); +} + +void BbCameraMediaRecorderControl::setMuted(bool muted) +{ + const int result = audio_manager_set_input_mute(currentAudioInputDevice(), muted); + if (result != EOK) { + emit error(QMediaRecorder::ResourceError, tr("Unable to set mute status")); + } else { + emit mutedChanged(muted); + } +} + +void BbCameraMediaRecorderControl::setVolume(qreal volume) +{ + const int result = audio_manager_set_input_level(currentAudioInputDevice(), (volume * 100)); + if (result != EOK) { + emit error(QMediaRecorder::ResourceError, tr("Unable to set audio input volume")); + } else { + emit volumeChanged(volume); + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol_p.h new file mode 100644 index 000000000..f1c6a0873 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameramediarecordercontrol_p.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAMEDIARECORDERCONTROL_H +#define BBCAMERAMEDIARECORDERCONTROL_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 BbCameraSession; + +class BbCameraMediaRecorderControl : public QMediaRecorderControl +{ + Q_OBJECT +public: + explicit BbCameraMediaRecorderControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraorientationhandler.cpp b/src/multimedia/platform/qnx/camera/bbcameraorientationhandler.cpp new file mode 100644 index 000000000..d600f3db0 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraorientationhandler.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraorientationhandler.h" + +#include <QAbstractEventDispatcher> +#include <QGuiApplication> +#include <QScreen> +#include <QDebug> + +#include <bps/orientation.h> + +QT_BEGIN_NAMESPACE + +BbCameraOrientationHandler::BbCameraOrientationHandler(QObject *parent) + : QObject(parent) + , m_orientation(0) +{ + QCoreApplication::eventDispatcher()->installNativeEventFilter(this); + int result = orientation_request_events(0); + if (result == BPS_FAILURE) + qWarning() << "Unable to register for orientation change events"; + + orientation_direction_t direction = ORIENTATION_FACE_UP; + int angle = 0; + + result = orientation_get(&direction, &angle); + if (result == BPS_FAILURE) { + qWarning() << "Unable to retrieve initial orientation"; + } else { + m_orientation = angle; + } +} + +BbCameraOrientationHandler::~BbCameraOrientationHandler() +{ + const int result = orientation_stop_events(0); + if (result == BPS_FAILURE) + qWarning() << "Unable to unregister for orientation change events"; + + QCoreApplication::eventDispatcher()->removeNativeEventFilter(this); +} + +bool BbCameraOrientationHandler::nativeEventFilter(const QByteArray&, void *message, qintptr *) +{ + bps_event_t* const event = static_cast<bps_event_t*>(message); + if (!event || bps_event_get_domain(event) != orientation_get_domain()) + return false; + + const int angle = orientation_event_get_angle(event); + if (angle != m_orientation) { + if (angle == 180) // The screen does not rotate at 180 degrees + return false; + + m_orientation = angle; + emit orientationChanged(m_orientation); + } + + return false; // do not drop the event +} + +int BbCameraOrientationHandler::viewfinderOrientation() const +{ + // On a keyboard device we do not rotate the screen at all + if (qGuiApp->primaryScreen()->nativeOrientation() + != qGuiApp->primaryScreen()->primaryOrientation()) { + return m_orientation; + } + + return 0; +} + +int BbCameraOrientationHandler::orientation() const +{ + return m_orientation; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraorientationhandler_p.h b/src/multimedia/platform/qnx/camera/bbcameraorientationhandler_p.h new file mode 100644 index 000000000..ace5118ef --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraorientationhandler_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAORIENTATIONHANDLER_H +#define BBCAMERAORIENTATIONHANDLER_H + +#include <QAbstractNativeEventFilter> +#include <QObject> + +// +// 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. +// + +QT_BEGIN_NAMESPACE + +class BbCameraOrientationHandler : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT +public: + explicit BbCameraOrientationHandler(QObject *parent = 0); + ~BbCameraOrientationHandler(); + + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; + + int orientation() const; + + int viewfinderOrientation() const; + +Q_SIGNALS: + void orientationChanged(int degree); + +private: + int m_orientation; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraservice.cpp b/src/multimedia/platform/qnx/camera/bbcameraservice.cpp new file mode 100644 index 000000000..860a7f5f4 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraservice.cpp @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraservice_p.h" + +#include "bbcameraaudioencodersettingscontrol_p.h" +#include "bbcameracontrol_p.h" +#include "bbcameraexposurecontrol_p.h" +#include "bbcamerafocuscontrol_p.h" +#include "bbcameraimagecapturecontrol_p.h" +#include "bbcameraimageprocessingcontrol_p.h" +#include "bbcameramediarecordercontrol_p.h" +#include "bbcamerasession_p.h" +#include "bbcameravideoencodersettingscontrol_p.h" +#include "bbcameraviewfindersettingscontrol_p.h" +#include "bbimageencodercontrol_p.h" +#include "bbvideodeviceselectorcontrol_p.h" +#include "bbvideorenderercontrol_p.h" + +#include <QDebug> +#include <QVariant> + +QT_BEGIN_NAMESPACE + +BbCameraService::BbCameraService(QObject *parent) + : QMediaService(parent) + , m_cameraSession(new BbCameraSession(this)) + , m_cameraAudioEncoderSettingsControl(new BbCameraAudioEncoderSettingsControl(m_cameraSession, this)) + , m_cameraControl(new BbCameraControl(m_cameraSession, this)) + , m_cameraExposureControl(new BbCameraExposureControl(m_cameraSession, this)) + , m_cameraFocusControl(new BbCameraFocusControl(m_cameraSession, this)) + , m_cameraImageCaptureControl(new BbCameraImageCaptureControl(m_cameraSession, this)) + , m_cameraImageProcessingControl(new BbCameraImageProcessingControl(m_cameraSession, this)) + , m_cameraMediaRecorderControl(new BbCameraMediaRecorderControl(m_cameraSession, this)) + , m_cameraVideoEncoderSettingsControl(new BbCameraVideoEncoderSettingsControl(m_cameraSession, this)) + , m_cameraViewfinderSettingsControl(new BbCameraViewfinderSettingsControl(m_cameraSession, this)) + , m_imageEncoderControl(new BbImageEncoderControl(m_cameraSession, this)) + , m_videoDeviceSelectorControl(new BbVideoDeviceSelectorControl(m_cameraSession, this)) + , m_videoRendererControl(new BbVideoRendererControl(m_cameraSession, this)) +{ +} + +BbCameraService::~BbCameraService() +{ +} + +QMediaControl* BbCameraService::requestControl(const char *name) +{ + if (qstrcmp(name, QAudioEncoderSettingsControl_iid) == 0) + return m_cameraAudioEncoderSettingsControl; + else if (qstrcmp(name, QCameraControl_iid) == 0) + return m_cameraControl; + else if (qstrcmp(name, QCameraExposureControl_iid) == 0) + return m_cameraExposureControl; + else if (qstrcmp(name, QCameraFocusControl_iid) == 0) + return m_cameraFocusControl; + else if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_cameraImageCaptureControl; + else if (qstrcmp(name, QCameraImageProcessingControl_iid) == 0) + return m_cameraImageProcessingControl; + else if (qstrcmp(name, QMediaRecorderControl_iid) == 0) + return m_cameraMediaRecorderControl; + else if (qstrcmp(name, QVideoEncoderSettingsControl_iid) == 0) + return m_cameraVideoEncoderSettingsControl; + else if (qstrcmp(name, QCameraViewfinderSettingsControl_iid) == 0) + return m_cameraViewfinderSettingsControl; + else if (qstrcmp(name, QImageEncoderControl_iid) == 0) + return m_imageEncoderControl; + else if (qstrcmp(name, QVideoDeviceSelectorControl_iid) == 0) + return m_videoDeviceSelectorControl; + else if (qstrcmp(name, QVideoRendererControl_iid) == 0) + return m_videoRendererControl; + + return 0; +} + +void BbCameraService::releaseControl(QMediaControl *control) +{ + Q_UNUSED(control); + + // Implemented as a singleton, so we do nothing. +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraservice_p.h b/src/multimedia/platform/qnx/camera/bbcameraservice_p.h new file mode 100644 index 000000000..6f172d8f5 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraservice_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERASERVICE_H +#define BBCAMERASERVICE_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 <qmediaservice.h> + +QT_BEGIN_NAMESPACE + +class BbCameraAudioEncoderSettingsControl; +class BbCameraControl; +class BbCameraExposureControl; +class BbCameraFocusControl; +class BbCameraImageCaptureControl; +class BbCameraImageProcessingControl; +class BbCameraMediaRecorderControl; +class BbCameraSession; +class BbCameraVideoEncoderSettingsControl; +class BbCameraViewfinderSettingsControl; +class BbImageEncoderControl; +class BbVideoDeviceSelectorControl; +class BbVideoRendererControl; + +class BbCameraService : public QMediaService +{ + Q_OBJECT + +public: + explicit BbCameraService(QObject *parent = 0); + ~BbCameraService(); + + virtual QMediaControl* requestControl(const char *name); + virtual void releaseControl(QMediaControl *control); + +private: + BbCameraSession* m_cameraSession; + + BbCameraAudioEncoderSettingsControl* m_cameraAudioEncoderSettingsControl; + BbCameraControl* m_cameraControl; + BbCameraExposureControl* m_cameraExposureControl; + BbCameraFocusControl* m_cameraFocusControl; + BbCameraImageCaptureControl* m_cameraImageCaptureControl; + BbCameraImageProcessingControl* m_cameraImageProcessingControl; + BbCameraMediaRecorderControl* m_cameraMediaRecorderControl; + BbCameraVideoEncoderSettingsControl* m_cameraVideoEncoderSettingsControl; + BbCameraViewfinderSettingsControl* m_cameraViewfinderSettingsControl; + BbImageEncoderControl* m_imageEncoderControl; + BbVideoDeviceSelectorControl* m_videoDeviceSelectorControl; + BbVideoRendererControl* m_videoRendererControl; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcamerasession.cpp b/src/multimedia/platform/qnx/camera/bbcamerasession.cpp new file mode 100644 index 000000000..866a9bb09 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcamerasession.cpp @@ -0,0 +1,1154 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcamerasession_p.h" + +#include "bbcameraorientationhandler_p.h" +#include "bbcameraviewfindersettingscontrol_p.h" +#include "windowgrabber_p.h" + +#include <QAbstractVideoSurface> +#include <QBuffer> +#include <QDebug> +#include <QImage> +#include <QUrl> +#include <QVideoSurfaceFormat> +#include <qmath.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +static QString errorToString(camera_error_t error) +{ + switch (error) { + case CAMERA_EOK: + return QLatin1String("No error"); + case CAMERA_EAGAIN: + return QLatin1String("Camera unavailable"); + case CAMERA_EINVAL: + return QLatin1String("Invalid argument"); + case CAMERA_ENODEV: + return QLatin1String("Camera not found"); + case CAMERA_EMFILE: + return QLatin1String("File table overflow"); + case CAMERA_EBADF: + return QLatin1String("Invalid handle passed"); + case CAMERA_EACCESS: + return QLatin1String("No permission"); + case CAMERA_EBADR: + return QLatin1String("Invalid file descriptor"); + case CAMERA_ENOENT: + return QLatin1String("File or directory does not exists"); + case CAMERA_ENOMEM: + return QLatin1String("Memory allocation failed"); + case CAMERA_EOPNOTSUPP: + return QLatin1String("Operation not supported"); + case CAMERA_ETIMEDOUT: + return QLatin1String("Communication timeout"); + case CAMERA_EALREADY: + return QLatin1String("Operation already in progress"); + case CAMERA_EUNINIT: + return QLatin1String("Camera library not initialized"); + case CAMERA_EREGFAULT: + return QLatin1String("Callback registration failed"); + case CAMERA_EMICINUSE: + return QLatin1String("Microphone in use already"); + case CAMERA_ENODATA: + return QLatin1String("Data does not exist"); + case CAMERA_EBUSY: + return QLatin1String("Camera busy"); + case CAMERA_EDESKTOPCAMERAINUSE: + return QLatin1String("Desktop camera in use already"); + case CAMERA_ENOSPC: + return QLatin1String("Disk is full"); + case CAMERA_EPOWERDOWN: + return QLatin1String("Camera in power down state"); + case CAMERA_3ALOCKED: + return QLatin1String("3A have been locked"); +// case CAMERA_EVIEWFINDERFROZEN: // not yet available in 10.2 NDK +// return QLatin1String("Freeze flag set"); + default: + return QLatin1String("Unknown error"); + } +} + +QDebug operator<<(QDebug debug, camera_error_t error) +{ + debug.nospace() << errorToString(error); + return debug.space(); +} + +BbCameraSession::BbCameraSession(QObject *parent) + : QObject(parent) + , m_nativeCameraOrientation(0) + , m_orientationHandler(new BbCameraOrientationHandler(this)) + , m_status(QCamera::UnloadedStatus) + , m_state(QCamera::UnloadedState) + , m_captureMode(QCamera::CaptureStillImage) + , m_device("bb:RearCamera") + , m_previewIsVideo(true) + , m_surface(0) + , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture) + , m_lastImageCaptureId(0) + , m_captureDestination(QCameraImageCapture::CaptureToFile) + , m_videoState(QMediaRecorder::StoppedState) + , m_videoStatus(QMediaRecorder::LoadedStatus) + , m_handle(CAMERA_HANDLE_INVALID) + , m_windowGrabber(new WindowGrabber(this)) +{ + connect(this, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyForCapture())); + connect(this, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateReadyForCapture())); + connect(m_orientationHandler, SIGNAL(orientationChanged(int)), SLOT(deviceOrientationChanged(int))); + + connect(m_windowGrabber, SIGNAL(frameGrabbed(QImage, int)), SLOT(viewfinderFrameGrabbed(QImage))); +} + +BbCameraSession::~BbCameraSession() +{ + stopViewFinder(); + closeCamera(); +} + +camera_handle_t BbCameraSession::handle() const +{ + return m_handle; +} + +QCamera::State BbCameraSession::state() const +{ + return m_state; +} + +void BbCameraSession::setState(QCamera::State state) +{ + if (m_state == state) + return; + + const QCamera::State previousState = m_state; + + if (previousState == QCamera::UnloadedState) { + if (state == QCamera::LoadedState) { + if (openCamera()) { + m_state = state; + } + } else if (state == QCamera::ActiveState) { + if (openCamera()) { + QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection); + m_state = state; + } + } + } else if (previousState == QCamera::LoadedState) { + if (state == QCamera::UnloadedState) { + closeCamera(); + m_state = state; + } else if (state == QCamera::ActiveState) { + QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection); + m_state = state; + } + } else if (previousState == QCamera::ActiveState) { + if (state == QCamera::LoadedState) { + stopViewFinder(); + m_state = state; + } else if (state == QCamera::UnloadedState) { + stopViewFinder(); + closeCamera(); + m_state = state; + } + } + + if (m_state != previousState) + emit stateChanged(m_state); +} + +QCamera::Status BbCameraSession::status() const +{ + return m_status; +} + +QCamera::CaptureModes BbCameraSession::captureMode() const +{ + return m_captureMode; +} + +void BbCameraSession::setCaptureMode(QCamera::CaptureModes captureMode) +{ + if (m_captureMode == captureMode) + return; + + m_captureMode = captureMode; + emit captureModeChanged(m_captureMode); +} + +bool BbCameraSession::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + if (m_handle == CAMERA_HANDLE_INVALID) { + // the camera has not been loaded yet via QCamera::load(), so + // we open it temporarily to peek for the supported capture modes + + camera_unit_t unit = CAMERA_UNIT_REAR; + if (m_device == cameraIdentifierFront()) + unit = CAMERA_UNIT_FRONT; + else if (m_device == cameraIdentifierRear()) + unit = CAMERA_UNIT_REAR; + else if (m_device == cameraIdentifierDesktop()) + unit = CAMERA_UNIT_DESKTOP; + + camera_handle_t handle; + const camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &handle); + if (result != CAMERA_EOK) + return true; + + const bool supported = isCaptureModeSupported(handle, mode); + + camera_close(handle); + + return supported; + } else { + return isCaptureModeSupported(m_handle, mode); + } +} + +QByteArray BbCameraSession::cameraIdentifierFront() +{ + return "bb:FrontCamera"; +} + +QByteArray BbCameraSession::cameraIdentifierRear() +{ + return "bb:RearCamera"; +} + +QByteArray BbCameraSession::cameraIdentifierDesktop() +{ + return "bb:DesktopCamera"; +} + +void BbCameraSession::setDevice(const QByteArray &device) +{ + m_device = device; +} + +QByteArray BbCameraSession::device() const +{ + return m_device; +} + +QAbstractVideoSurface* BbCameraSession::surface() const +{ + return m_surface; +} + +void BbCameraSession::setSurface(QAbstractVideoSurface *surface) +{ + QMutexLocker locker(&m_surfaceMutex); + + if (m_surface == surface) + return; + + m_surface = surface; +} + +bool BbCameraSession::isReadyForCapture() const +{ + if (m_captureMode & QCamera::CaptureStillImage) + return (m_status == QCamera::ActiveStatus); + + if (m_captureMode & QCamera::CaptureVideo) + return (m_status == QCamera::ActiveStatus); + + return false; +} + +QCameraImageCapture::DriveMode BbCameraSession::driveMode() const +{ + return m_captureImageDriveMode; +} + +void BbCameraSession::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_captureImageDriveMode = mode; +} + +/** + * A helper structure that keeps context data for image capture callbacks. + */ +struct ImageCaptureData +{ + int requestId; + QString fileName; + BbCameraSession *session; +}; + +static void imageCaptureShutterCallback(camera_handle_t handle, void *context) +{ + Q_UNUSED(handle); + + const ImageCaptureData *data = static_cast<ImageCaptureData*>(context); + + // We are inside a worker thread here, so emit imageExposed inside the main thread + QMetaObject::invokeMethod(data->session, "imageExposed", Qt::QueuedConnection, + Q_ARG(int, data->requestId)); +} + +static void imageCaptureImageCallback(camera_handle_t handle, camera_buffer_t *buffer, void *context) +{ + Q_UNUSED(handle); + + QScopedPointer<ImageCaptureData> data(static_cast<ImageCaptureData*>(context)); + + if (buffer->frametype != CAMERA_FRAMETYPE_JPEG) { + // We are inside a worker thread here, so emit error signal inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError), + Q_ARG(QString, BbCameraSession::tr("Camera provides image in unsupported format"))); + return; + } + + const QByteArray rawData((const char*)buffer->framebuf, buffer->framedesc.jpeg.bufsize); + + QImage image; + const bool ok = image.loadFromData(rawData, "JPG"); + if (!ok) { + const QString errorMessage = BbCameraSession::tr("Could not load JPEG data from frame"); + // We are inside a worker thread here, so emit error signal inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError), + Q_ARG(QString, errorMessage)); + return; + } + + + // We are inside a worker thread here, so invoke imageCaptured inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptured", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QImage, image), + Q_ARG(QString, data->fileName)); +} + +int BbCameraSession::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) { + // prepare context object for callback + ImageCaptureData *context = new ImageCaptureData; + context->requestId = m_lastImageCaptureId; + context->fileName = fileName; + context->session = this; + + const camera_error_t result = camera_take_photo(m_handle, + imageCaptureShutterCallback, + 0, + 0, + imageCaptureImageCallback, + context, false); + + if (result != CAMERA_EOK) + qWarning() << "Unable to take photo:" << result; + } else { + // TODO: implement burst mode when available in Qt API + } + + return m_lastImageCaptureId; +} + +void BbCameraSession::cancelCapture() +{ + // BB10 API doesn't provide functionality for that +} + +bool BbCameraSession::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const +{ + // capture to buffer, file and both are supported. + return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer); +} + +QCameraImageCapture::CaptureDestinations BbCameraSession::captureDestination() const +{ + return m_captureDestination; +} + +void BbCameraSession::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + if (m_captureDestination != destination) { + m_captureDestination = destination; + emit captureDestinationChanged(m_captureDestination); + } +} + +QList<QSize> BbCameraSession::supportedResolutions(const QImageEncoderSettings&, bool *continuous) const +{ + if (continuous) + *continuous = false; + + if (m_status == QCamera::UnloadedStatus) + return QList<QSize>(); + + if (m_captureMode & QCamera::CaptureStillImage) { + return supportedResolutions(QCamera::CaptureStillImage); + } else if (m_captureMode & QCamera::CaptureVideo) { + return supportedResolutions(QCamera::CaptureVideo); + } + + return QList<QSize>(); +} + +QImageEncoderSettings BbCameraSession::imageSettings() const +{ + return m_imageEncoderSettings; +} + +void BbCameraSession::setImageSettings(const QImageEncoderSettings &settings) +{ + m_imageEncoderSettings = settings; + if (m_imageEncoderSettings.codec().isEmpty()) + m_imageEncoderSettings.setCodec(QLatin1String("jpeg")); +} + +QUrl BbCameraSession::outputLocation() const +{ + return QUrl::fromLocalFile(m_videoOutputLocation); +} + +bool BbCameraSession::setOutputLocation(const QUrl &location) +{ + m_videoOutputLocation = location.toLocalFile(); + + return true; +} + +QMediaRecorder::State BbCameraSession::videoState() const +{ + return m_videoState; +} + +void BbCameraSession::setVideoState(QMediaRecorder::State state) +{ + if (m_videoState == state) + return; + + const QMediaRecorder::State previousState = m_videoState; + + if (previousState == QMediaRecorder::StoppedState) { + if (state == QMediaRecorder::RecordingState) { + if (startVideoRecording()) { + m_videoState = state; + } + } else if (state == QMediaRecorder::PausedState) { + // do nothing + } + } else if (previousState == QMediaRecorder::RecordingState) { + if (state == QMediaRecorder::StoppedState) { + stopVideoRecording(); + m_videoState = state; + } else if (state == QMediaRecorder::PausedState) { + //TODO: (pause) not supported by BB10 API yet + } + } else if (previousState == QMediaRecorder::PausedState) { + if (state == QMediaRecorder::StoppedState) { + stopVideoRecording(); + m_videoState = state; + } else if (state == QMediaRecorder::RecordingState) { + //TODO: (resume) not supported by BB10 API yet + } + } + + emit videoStateChanged(m_videoState); +} + +QMediaRecorder::Status BbCameraSession::videoStatus() const +{ + return m_videoStatus; +} + +qint64 BbCameraSession::duration() const +{ + return (m_videoRecordingDuration.isValid() ? m_videoRecordingDuration.elapsed() : 0); +} + +void BbCameraSession::applyVideoSettings() +{ + if (m_handle == CAMERA_HANDLE_INVALID) + return; + + // apply viewfinder configuration + const QList<QSize> videoOutputResolutions = supportedResolutions(QCamera::CaptureVideo); + + if (!m_videoEncoderSettings.resolution().isValid() || !videoOutputResolutions.contains(m_videoEncoderSettings.resolution())) + m_videoEncoderSettings.setResolution(videoOutputResolutions.first()); + + QSize viewfinderResolution; + + if (m_previewIsVideo) { + // The viewfinder is responsible for encoding the video frames, so the resolutions must match. + viewfinderResolution = m_videoEncoderSettings.resolution(); + } else { + // The frames are encoded separately from the viewfinder, so only the aspect ratio must match. + const QSize videoResolution = m_videoEncoderSettings.resolution(); + const qreal aspectRatio = static_cast<qreal>(videoResolution.width())/static_cast<qreal>(videoResolution.height()); + + QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureVideo); + std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution + for (const QSize &size : qAsConst(sizes)) { + // search for viewfinder resolution with the same aspect ratio + if (qFuzzyCompare(aspectRatio, (static_cast<qreal>(size.width())/static_cast<qreal>(size.height())))) { + viewfinderResolution = size; + break; + } + } + } + + Q_ASSERT(viewfinderResolution.isValid()); + + const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1(); + m_windowGrabber->setWindowId(windowId); + + const QByteArray windowGroupId = m_windowGrabber->windowGroupId(); + + const int rotationAngle = (360 - m_nativeCameraOrientation); + + camera_error_t result = CAMERA_EOK; + result = camera_set_videovf_property(m_handle, + CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(), + CAMERA_IMGPROP_WIN_ID, windowId.data(), + CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(), + CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(), + CAMERA_IMGPROP_ROTATION, rotationAngle); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply video viewfinder settings:" << result; + return; + } + + const QSize resolution = m_videoEncoderSettings.resolution(); + + QString videoCodec = m_videoEncoderSettings.codec(); + if (videoCodec.isEmpty()) + videoCodec = QLatin1String("h264"); + + camera_videocodec_t cameraVideoCodec = CAMERA_VIDEOCODEC_H264; + if (videoCodec == QLatin1String("none")) + cameraVideoCodec = CAMERA_VIDEOCODEC_NONE; + else if (videoCodec == QLatin1String("avc1")) + cameraVideoCodec = CAMERA_VIDEOCODEC_AVC1; + else if (videoCodec == QLatin1String("h264")) + cameraVideoCodec = CAMERA_VIDEOCODEC_H264; + + qreal frameRate = m_videoEncoderSettings.frameRate(); + if (frameRate == 0) { + const QList<qreal> frameRates = supportedFrameRates(QVideoEncoderSettings(), 0); + if (!frameRates.isEmpty()) + frameRate = frameRates.last(); + } + + QString audioCodec = m_audioEncoderSettings.codec(); + if (audioCodec.isEmpty()) + audioCodec = QLatin1String("aac"); + + camera_audiocodec_t cameraAudioCodec = CAMERA_AUDIOCODEC_AAC; + if (audioCodec == QLatin1String("none")) + cameraAudioCodec = CAMERA_AUDIOCODEC_NONE; + else if (audioCodec == QLatin1String("aac")) + cameraAudioCodec = CAMERA_AUDIOCODEC_AAC; + else if (audioCodec == QLatin1String("raw")) + cameraAudioCodec = CAMERA_AUDIOCODEC_RAW; + + result = camera_set_video_property(m_handle, + CAMERA_IMGPROP_WIDTH, resolution.width(), + CAMERA_IMGPROP_HEIGHT, resolution.height(), + CAMERA_IMGPROP_ROTATION, rotationAngle, + CAMERA_IMGPROP_VIDEOCODEC, cameraVideoCodec, + CAMERA_IMGPROP_AUDIOCODEC, cameraAudioCodec); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply video settings:" << result; + emit videoError(QMediaRecorder::ResourceError, tr("Unable to apply video settings")); + } +} + +QList<QSize> BbCameraSession::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (continuous) + *continuous = false; + + return supportedResolutions(QCamera::CaptureVideo); +} + +QList<qreal> BbCameraSession::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (m_handle == CAMERA_HANDLE_INVALID) + return QList<qreal>(); + + int supported = 0; + double rates[20]; + bool maxmin = false; + + /** + * Since in current version of the BB10 platform the video viewfinder encodes the video frames, we use + * the values as returned by camera_get_video_vf_framerates(). + */ + const camera_error_t result = camera_get_video_vf_framerates(m_handle, 20, &supported, rates, &maxmin); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve supported viewfinder framerates:" << result; + return QList<qreal>(); + } + + QList<qreal> frameRates; + for (int i = 0; i < supported; ++i) + frameRates << rates[i]; + + if (continuous) + *continuous = maxmin; + + return frameRates; +} + +QVideoEncoderSettings BbCameraSession::videoSettings() const +{ + return m_videoEncoderSettings; +} + +void BbCameraSession::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoEncoderSettings = settings; +} + +QAudioEncoderSettings BbCameraSession::audioSettings() const +{ + return m_audioEncoderSettings; +} + +void BbCameraSession::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_audioEncoderSettings = settings; +} + +void BbCameraSession::updateReadyForCapture() +{ + emit readyForCaptureChanged(isReadyForCapture()); +} + +void BbCameraSession::imageCaptured(int requestId, const QImage &rawImage, const QString &fileName) +{ + QTransform transform; + + // subtract out the native rotation + transform.rotate(m_nativeCameraOrientation); + + // subtract out the current device orientation + if (m_device == cameraIdentifierRear()) + transform.rotate(360 - m_orientationHandler->orientation()); + else + transform.rotate(m_orientationHandler->orientation()); + + const QImage image = rawImage.transformed(transform); + + // Generate snap preview as downscaled image + { + QSize previewSize = image.size(); + int downScaleSteps = 0; + while (previewSize.width() > 800 && downScaleSteps < 8) { + previewSize.rwidth() /= 2; + previewSize.rheight() /= 2; + downScaleSteps++; + } + + const QImage snapPreview = image.scaled(previewSize); + + emit imageCaptured(requestId, snapPreview); + } + + if (m_captureDestination & QCameraImageCapture::CaptureToBuffer) { + QVideoFrame frame(image); + + emit imageAvailable(requestId, frame); + } + + if (m_captureDestination & QCameraImageCapture::CaptureToFile) { + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QCamera::CaptureStillImage, + QLatin1String("IMG_"), + QLatin1String("jpg")); + + QFile file(actualFileName); + if (file.open(QFile::WriteOnly)) { + if (image.save(&file, "JPG")) { + emit imageSaved(requestId, actualFileName); + } else { + emit imageCaptureError(requestId, QCameraImageCapture::OutOfSpaceError, file.errorString()); + } + } else { + const QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName); + emit imageCaptureError(requestId, QCameraImageCapture::ResourceError, errorMessage); + } + } +} + +void BbCameraSession::handleVideoRecordingPaused() +{ + //TODO: implement once BB10 API supports pausing a video +} + +void BbCameraSession::handleVideoRecordingResumed() +{ + if (m_videoStatus == QMediaRecorder::StartingStatus) { + m_videoStatus = QMediaRecorder::RecordingStatus; + emit videoStatusChanged(m_videoStatus); + + m_videoRecordingDuration.restart(); + } +} + +void BbCameraSession::deviceOrientationChanged(int angle) +{ + if (m_handle != CAMERA_HANDLE_INVALID) + camera_set_device_orientation(m_handle, angle); +} + +void BbCameraSession::handleCameraPowerUp() +{ + stopViewFinder(); + startViewFinder(); +} + +void BbCameraSession::viewfinderFrameGrabbed(const QImage &image) +{ + QTransform transform; + + // subtract out the native rotation + transform.rotate(m_nativeCameraOrientation); + + // subtract out the current device orientation + if (m_device == cameraIdentifierRear()) + transform.rotate(360 - m_orientationHandler->viewfinderOrientation()); + else + transform.rotate(m_orientationHandler->viewfinderOrientation()); + + QImage frame = image.copy().transformed(transform); + + QMutexLocker locker(&m_surfaceMutex); + if (m_surface) { + if (frame.size() != m_surface->surfaceFormat().frameSize()) { + m_surface->stop(); + m_surface->start(QVideoSurfaceFormat(frame.size(), QVideoFrame::Format_ARGB32)); + } + + QVideoFrame videoFrame(frame); + + m_surface->present(videoFrame); + } +} + +bool BbCameraSession::openCamera() +{ + if (m_handle != CAMERA_HANDLE_INVALID) // camera is already open + return true; + + m_status = QCamera::LoadingStatus; + emit statusChanged(m_status); + + camera_unit_t unit = CAMERA_UNIT_REAR; + if (m_device == cameraIdentifierFront()) + unit = CAMERA_UNIT_FRONT; + else if (m_device == cameraIdentifierRear()) + unit = CAMERA_UNIT_REAR; + else if (m_device == cameraIdentifierDesktop()) + unit = CAMERA_UNIT_DESKTOP; + + camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &m_handle); + if (result != CAMERA_EOK) { + m_handle = CAMERA_HANDLE_INVALID; + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); + + qWarning() << "Unable to open camera:" << result; + emit error(QCamera::CameraError, tr("Unable to open camera")); + return false; + } + + result = camera_get_native_orientation(m_handle, &m_nativeCameraOrientation); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve native camera orientation:" << result; + emit error(QCamera::CameraError, tr("Unable to retrieve native camera orientation")); + return false; + } + + m_previewIsVideo = camera_has_feature(m_handle, CAMERA_FEATURE_PREVIEWISVIDEO); + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + emit cameraOpened(); + + return true; +} + +void BbCameraSession::closeCamera() +{ + if (m_handle == CAMERA_HANDLE_INVALID) // camera is closed already + return; + + m_status = QCamera::UnloadingStatus; + emit statusChanged(m_status); + + const camera_error_t result = camera_close(m_handle); + if (result != CAMERA_EOK) { + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + qWarning() << "Unable to close camera:" << result; + emit error(QCamera::CameraError, tr("Unable to close camera")); + return; + } + + m_handle = CAMERA_HANDLE_INVALID; + + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); +} + +static void viewFinderStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context) +{ + Q_UNUSED(handle); + + if (status == CAMERA_STATUS_FOCUS_CHANGE) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "focusStatusChanged", Qt::QueuedConnection, Q_ARG(int, value)); + return; + } else if (status == CAMERA_STATUS_POWERUP) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleCameraPowerUp", Qt::QueuedConnection); + } +} + +bool BbCameraSession::startViewFinder() +{ + m_status = QCamera::StartingStatus; + emit statusChanged(m_status); + + QSize viewfinderResolution; + camera_error_t result = CAMERA_EOK; + if (m_captureMode & QCamera::CaptureStillImage) { + result = camera_start_photo_viewfinder(m_handle, 0, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureStillImage); + } else if (m_captureMode & QCamera::CaptureVideo) { + result = camera_start_video_viewfinder(m_handle, 0, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureVideo); + } + + if (result != CAMERA_EOK) { + qWarning() << "Unable to start viewfinder:" << result; + return false; + } + + const int angle = m_orientationHandler->viewfinderOrientation(); + + const QSize rotatedSize = ((angle == 0 || angle == 180) ? viewfinderResolution + : viewfinderResolution.transposed()); + + m_surfaceMutex.lock(); + if (m_surface) { + const bool ok = m_surface->start(QVideoSurfaceFormat(rotatedSize, QVideoFrame::Format_ARGB32)); + if (!ok) + qWarning() << "Unable to start camera viewfinder surface"; + } + m_surfaceMutex.unlock(); + + m_status = QCamera::ActiveStatus; + emit statusChanged(m_status); + + return true; +} + +void BbCameraSession::stopViewFinder() +{ + m_windowGrabber->stop(); + + m_status = QCamera::StoppingStatus; + emit statusChanged(m_status); + + m_surfaceMutex.lock(); + if (m_surface) { + m_surface->stop(); + } + m_surfaceMutex.unlock(); + + camera_error_t result = CAMERA_EOK; + if (m_captureMode & QCamera::CaptureStillImage) + result = camera_stop_photo_viewfinder(m_handle); + else if (m_captureMode & QCamera::CaptureVideo) + result = camera_stop_video_viewfinder(m_handle); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to stop viewfinder:" << result; + return; + } + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); +} + +void BbCameraSession::applyConfiguration() +{ + if (m_captureMode & QCamera::CaptureStillImage) { + const QList<QSize> photoOutputResolutions = supportedResolutions(QCamera::CaptureStillImage); + + if (!m_imageEncoderSettings.resolution().isValid() || !photoOutputResolutions.contains(m_imageEncoderSettings.resolution())) + m_imageEncoderSettings.setResolution(photoOutputResolutions.first()); + + const QSize photoResolution = m_imageEncoderSettings.resolution(); + const qreal aspectRatio = static_cast<qreal>(photoResolution.width())/static_cast<qreal>(photoResolution.height()); + + // apply viewfinder configuration + QSize viewfinderResolution; + QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureStillImage); + std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution + for (const QSize &size : qAsConst(sizes)) { + // search for viewfinder resolution with the same aspect ratio + if (qFuzzyCompare(aspectRatio, (static_cast<qreal>(size.width())/static_cast<qreal>(size.height())))) { + viewfinderResolution = size; + break; + } + } + + Q_ASSERT(viewfinderResolution.isValid()); + + const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1(); + m_windowGrabber->setWindowId(windowId); + + const QByteArray windowGroupId = m_windowGrabber->windowGroupId(); + + camera_error_t result = camera_set_photovf_property(m_handle, + CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(), + CAMERA_IMGPROP_WIN_ID, windowId.data(), + CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(), + CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(), + CAMERA_IMGPROP_FORMAT, CAMERA_FRAMETYPE_NV12, + CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply photo viewfinder settings:" << result; + return; + } + + + int jpegQuality = 100; + switch (m_imageEncoderSettings.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; + } + + // apply photo configuration + result = camera_set_photo_property(m_handle, + CAMERA_IMGPROP_WIDTH, photoResolution.width(), + CAMERA_IMGPROP_HEIGHT, photoResolution.height(), + CAMERA_IMGPROP_JPEGQFACTOR, jpegQuality, + CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply photo settings:" << result; + return; + } + + } else if (m_captureMode & QCamera::CaptureVideo) { + applyVideoSettings(); + } + + startViewFinder(); +} + +static void videoRecordingStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context) +{ + Q_UNUSED(handle); + Q_UNUSED(value); + + if (status == CAMERA_STATUS_VIDEO_PAUSE) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleVideoRecordingPaused", Qt::QueuedConnection); + } else if (status == CAMERA_STATUS_VIDEO_RESUME) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleVideoRecordingResumed", Qt::QueuedConnection); + } +} + +bool BbCameraSession::startVideoRecording() +{ + m_videoRecordingDuration.invalidate(); + + m_videoStatus = QMediaRecorder::StartingStatus; + emit videoStatusChanged(m_videoStatus); + + if (m_videoOutputLocation.isEmpty()) + m_videoOutputLocation = m_mediaStorageLocation.generateFileName(QLatin1String("VID_"), m_mediaStorageLocation.defaultDir(QCamera::CaptureVideo), QLatin1String("mp4")); + + emit actualLocationChanged(m_videoOutputLocation); + + const camera_error_t result = camera_start_video(m_handle, QFile::encodeName(m_videoOutputLocation), 0, videoRecordingStatusCallback, this); + if (result != CAMERA_EOK) { + m_videoStatus = QMediaRecorder::LoadedStatus; + emit videoStatusChanged(m_videoStatus); + + emit videoError(QMediaRecorder::ResourceError, tr("Unable to start video recording")); + return false; + } + + return true; +} + +void BbCameraSession::stopVideoRecording() +{ + m_videoStatus = QMediaRecorder::FinalizingStatus; + emit videoStatusChanged(m_videoStatus); + + const camera_error_t result = camera_stop_video(m_handle); + if (result != CAMERA_EOK) { + emit videoError(QMediaRecorder::ResourceError, tr("Unable to stop video recording")); + } + + m_videoStatus = QMediaRecorder::LoadedStatus; + emit videoStatusChanged(m_videoStatus); + + m_videoRecordingDuration.invalidate(); +} + +bool BbCameraSession::isCaptureModeSupported(camera_handle_t handle, QCamera::CaptureModes mode) const +{ + if (mode & QCamera::CaptureStillImage) + return camera_has_feature(handle, CAMERA_FEATURE_PHOTO); + + if (mode & QCamera::CaptureVideo) + return camera_has_feature(handle, CAMERA_FEATURE_VIDEO); + + return false; +} + +QList<QSize> BbCameraSession::supportedResolutions(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + QList<QSize> list; + + camera_error_t result = CAMERA_EOK; + camera_res_t resolutions[20]; + unsigned int supported = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photo_output_resolutions(m_handle, CAMERA_FRAMETYPE_JPEG, 20, &supported, resolutions); + else if (mode == QCamera::CaptureVideo) + result = camera_get_video_output_resolutions(m_handle, 20, &supported, resolutions); + + if (result != CAMERA_EOK) + return list; + + for (unsigned int i = 0; i < supported; ++i) + list << QSize(resolutions[i].width, resolutions[i].height); + + return list; +} + +QList<QSize> BbCameraSession::supportedViewfinderResolutions(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + QList<QSize> list; + + camera_error_t result = CAMERA_EOK; + camera_res_t resolutions[20]; + unsigned int supported = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photo_vf_resolutions(m_handle, 20, &supported, resolutions); + else if (mode == QCamera::CaptureVideo) + result = camera_get_video_vf_resolutions(m_handle, 20, &supported, resolutions); + + if (result != CAMERA_EOK) + return list; + + for (unsigned int i = 0; i < supported; ++i) + list << QSize(resolutions[i].width, resolutions[i].height); + + return list; +} + +QSize BbCameraSession::currentViewfinderResolution(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + camera_error_t result = CAMERA_EOK; + int width = 0; + int height = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + else if (mode == QCamera::CaptureVideo) + result = camera_get_videovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + + if (result != CAMERA_EOK) + return QSize(); + + return QSize(width, height); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcamerasession_p.h b/src/multimedia/platform/qnx/camera/bbcamerasession_p.h new file mode 100644 index 000000000..e49951141 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcamerasession_p.h @@ -0,0 +1,226 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERASESSION_H +#define BBCAMERASESSION_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 "bbmediastoragelocation_p.h" + +#include <QCamera> +#include <QCameraImageCapture> +#include <QCameraViewfinderSettingsControl> +#include <QElapsedTimer> +#include <QMediaRecorder> +#include <QMutex> +#include <QObject> +#include <QPointer> + +#include <camera/camera_api.h> + +QT_BEGIN_NAMESPACE + +class BbCameraOrientationHandler; +class WindowGrabber; + +class BbCameraSession : public QObject +{ + Q_OBJECT +public: + explicit BbCameraSession(QObject *parent = 0); + ~BbCameraSession(); + + camera_handle_t handle() const; + + // camera control + QCamera::State state() const; + void setState(QCamera::State state); + QCamera::Status status() const; + QCamera::CaptureModes captureMode() const; + void setCaptureMode(QCamera::CaptureModes); + bool isCaptureModeSupported(QCamera::CaptureModes mode) const; + + // video device selector control + static QByteArray cameraIdentifierFront(); + static QByteArray cameraIdentifierRear(); + static QByteArray cameraIdentifierDesktop(); + + void setDevice(const QByteArray &device); + QByteArray device() const; + + // video renderer control + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + // image capture control + bool isReadyForCapture() const; + QCameraImageCapture::DriveMode driveMode() const; + void setDriveMode(QCameraImageCapture::DriveMode mode); + int capture(const QString &fileName); + void cancelCapture(); + + // capture destination control + bool isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const; + QCameraImageCapture::CaptureDestinations captureDestination() const; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination); + + // image encoder control + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings, bool *continuous) const; + QImageEncoderSettings imageSettings() const; + void setImageSettings(const QImageEncoderSettings &settings); + + // media recorder control + QUrl outputLocation() const; + bool setOutputLocation(const QUrl &location); + QMediaRecorder::State videoState() const; + void setVideoState(QMediaRecorder::State state); + QMediaRecorder::Status videoStatus() const; + qint64 duration() const; + void applyVideoSettings(); + + // video encoder settings control + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const; + QList<qreal> supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const; + QVideoEncoderSettings videoSettings() const; + void setVideoSettings(const QVideoEncoderSettings &settings); + + // audio encoder settings control + QAudioEncoderSettings audioSettings() const; + void setAudioSettings(const QAudioEncoderSettings &settings); + +Q_SIGNALS: + // camera control + void statusChanged(QCamera::Status); + void stateChanged(QCamera::State); + void error(int error, const QString &errorString); + void captureModeChanged(QCamera::CaptureModes); + + // image capture control + 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); + + // capture destination control + void captureDestinationChanged(QCameraImageCapture::CaptureDestinations destination); + + // media recorder control + void videoStateChanged(QMediaRecorder::State state); + void videoStatusChanged(QMediaRecorder::Status status); + void durationChanged(qint64 duration); + void actualLocationChanged(const QUrl &location); + void videoError(int error, const QString &errorString); + + void cameraOpened(); + void focusStatusChanged(int status); + +private slots: + void updateReadyForCapture(); + void imageCaptured(int, const QImage&, const QString&); + void handleVideoRecordingPaused(); + void handleVideoRecordingResumed(); + void deviceOrientationChanged(int); + void handleCameraPowerUp(); + void viewfinderFrameGrabbed(const QImage &image); + void applyConfiguration(); + +private: + bool openCamera(); + void closeCamera(); + bool startViewFinder(); + void stopViewFinder(); + bool startVideoRecording(); + void stopVideoRecording(); + + bool isCaptureModeSupported(camera_handle_t handle, QCamera::CaptureModes mode) const; + QList<QSize> supportedResolutions(QCamera::CaptureMode mode) const; + QList<QSize> supportedViewfinderResolutions(QCamera::CaptureMode mode) const; + QSize currentViewfinderResolution(QCamera::CaptureMode mode) const; + + quint32 m_nativeCameraOrientation; + BbCameraOrientationHandler* m_orientationHandler; + + QCamera::Status m_status; + QCamera::State m_state; + QCamera::CaptureModes m_captureMode; + + QByteArray m_device; + bool m_previewIsVideo; + + QPointer<QAbstractVideoSurface> m_surface; + QMutex m_surfaceMutex; + + QCameraImageCapture::DriveMode m_captureImageDriveMode; + int m_lastImageCaptureId; + QCameraImageCapture::CaptureDestinations m_captureDestination; + + QImageEncoderSettings m_imageEncoderSettings; + + QString m_videoOutputLocation; + QMediaRecorder::State m_videoState; + QMediaRecorder::Status m_videoStatus; + QElapsedTimer m_videoRecordingDuration; + + QVideoEncoderSettings m_videoEncoderSettings; + QAudioEncoderSettings m_audioEncoderSettings; + + BbMediaStorageLocation m_mediaStorageLocation; + + camera_handle_t m_handle; + + WindowGrabber* m_windowGrabber; +}; + +QDebug operator<<(QDebug debug, camera_error_t error); + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol.cpp new file mode 100644 index 000000000..d16d7a307 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameravideoencodersettingscontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbCameraVideoEncoderSettingsControl::BbCameraVideoEncoderSettingsControl(BbCameraSession *session, QObject *parent) + : QVideoEncoderSettingsControl(parent) + , m_session(session) +{ +} + +QList<QSize> BbCameraVideoEncoderSettingsControl::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const +{ + return m_session->supportedResolutions(settings, continuous); +} + +QList<qreal> BbCameraVideoEncoderSettingsControl::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const +{ + return m_session->supportedFrameRates(settings, continuous); +} + +QStringList BbCameraVideoEncoderSettingsControl::supportedVideoCodecs() const +{ + return QStringList() << QLatin1String("none") << QLatin1String("avc1") << QLatin1String("h264"); +} + +QString BbCameraVideoEncoderSettingsControl::videoCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("none")) + return tr("No compression"); + else if (codecName == QLatin1String("avc1")) + return tr("AVC1 compression"); + else if (codecName == QLatin1String("h264")) + return tr("H264 compression"); + + return QString(); +} + +QVideoEncoderSettings BbCameraVideoEncoderSettingsControl::videoSettings() const +{ + return m_session->videoSettings(); +} + +void BbCameraVideoEncoderSettingsControl::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_session->setVideoSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol_p.h new file mode 100644 index 000000000..893b26d5d --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameravideoencodersettingscontrol_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAVIDEOENCODERSETTINGSCONTROL_H +#define BBCAMERAVIDEOENCODERSETTINGSCONTROL_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 BbCameraSession; + +class BbCameraVideoEncoderSettingsControl : public QVideoEncoderSettingsControl +{ + Q_OBJECT +public: + explicit BbCameraVideoEncoderSettingsControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol.cpp b/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol.cpp new file mode 100644 index 000000000..ada52ab6b --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol.cpp @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbcameraviewfindersettingscontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +BbCameraViewfinderSettingsControl::BbCameraViewfinderSettingsControl(BbCameraSession *session, QObject *parent) + : QCameraViewfinderSettingsControl(parent) + , m_session(session) +{ +} + +bool BbCameraViewfinderSettingsControl::isViewfinderParameterSupported(ViewfinderParameter parameter) const +{ + switch (parameter) { + case QCameraViewfinderSettingsControl::Resolution: + return true; + case QCameraViewfinderSettingsControl::PixelAspectRatio: + return false; + case QCameraViewfinderSettingsControl::MinimumFrameRate: + return true; + case QCameraViewfinderSettingsControl::MaximumFrameRate: + return true; + case QCameraViewfinderSettingsControl::PixelFormat: + return true; + default: + return false; + } +} + +QVariant BbCameraViewfinderSettingsControl::viewfinderParameter(ViewfinderParameter parameter) const +{ + if (parameter == QCameraViewfinderSettingsControl::Resolution) { + camera_error_t result = CAMERA_EOK; + unsigned int width = 0; + unsigned int height = 0; + + if (m_session->captureMode() & QCamera::CaptureStillImage) { + result = camera_get_photovf_property(m_session->handle(), CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + } else if (m_session->captureMode() & QCamera::CaptureVideo) { + result = camera_get_videovf_property(m_session->handle(), CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + } + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve resolution of viewfinder:" << result; + return QVariant(); + } + + return QSize(width, height); + + } else if (parameter == QCameraViewfinderSettingsControl::MinimumFrameRate) { + camera_error_t result = CAMERA_EOK; + double minimumFrameRate = 0; + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), CAMERA_IMGPROP_MINFRAMERATE, &minimumFrameRate); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), CAMERA_IMGPROP_MINFRAMERATE, &minimumFrameRate); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve minimum framerate of viewfinder:" << result; + return QVariant(); + } + + return QVariant(static_cast<qreal>(minimumFrameRate)); + + } else if (parameter == QCameraViewfinderSettingsControl::MaximumFrameRate) { + camera_error_t result = CAMERA_EOK; + double maximumFrameRate = 0; + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), CAMERA_IMGPROP_FRAMERATE, &maximumFrameRate); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), CAMERA_IMGPROP_FRAMERATE, &maximumFrameRate); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve maximum framerate of viewfinder:" << result; + return QVariant(); + } + + return QVariant(static_cast<qreal>(maximumFrameRate)); + } else if (parameter == QCameraViewfinderSettingsControl::PixelFormat) { + camera_error_t result = CAMERA_EOK; + camera_frametype_t format = CAMERA_FRAMETYPE_UNSPECIFIED; + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_session->handle(), CAMERA_IMGPROP_FORMAT, &format); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_get_videovf_property(m_session->handle(), CAMERA_IMGPROP_FORMAT, &format); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve pixel format of viewfinder:" << result; + return QVariant(); + } + + switch (format) { + case CAMERA_FRAMETYPE_UNSPECIFIED: + return QVideoFrame::Format_Invalid; + case CAMERA_FRAMETYPE_NV12: + return QVideoFrame::Format_NV12; + case CAMERA_FRAMETYPE_RGB8888: + return QVideoFrame::Format_ARGB32; + case CAMERA_FRAMETYPE_RGB888: + return QVideoFrame::Format_RGB24; + case CAMERA_FRAMETYPE_JPEG: + return QVideoFrame::Format_Jpeg; + case CAMERA_FRAMETYPE_GRAY8: + return QVideoFrame::Format_Y8; + case CAMERA_FRAMETYPE_METADATA: + return QVideoFrame::Format_Invalid; + case CAMERA_FRAMETYPE_BAYER: + return QVideoFrame::Format_Invalid; + case CAMERA_FRAMETYPE_CBYCRY: + return QVideoFrame::Format_Invalid; + case CAMERA_FRAMETYPE_COMPRESSEDVIDEO: + return QVideoFrame::Format_Invalid; + case CAMERA_FRAMETYPE_COMPRESSEDAUDIO: + return QVideoFrame::Format_Invalid; + default: + return QVideoFrame::Format_Invalid; + } + } + + return QVariant(); +} + +void BbCameraViewfinderSettingsControl::setViewfinderParameter(ViewfinderParameter parameter, const QVariant &value) +{ + if (parameter == QCameraViewfinderSettingsControl::Resolution) { + camera_error_t result = CAMERA_EOK; + const QSize size = value.toSize(); + + if (m_session->captureMode() & QCamera::CaptureStillImage) { + result = camera_set_photovf_property(m_session->handle(), CAMERA_IMGPROP_WIDTH, size.width(), + CAMERA_IMGPROP_HEIGHT, size.height()); + } else if (m_session->captureMode() & QCamera::CaptureVideo) { + result = camera_set_videovf_property(m_session->handle(), CAMERA_IMGPROP_WIDTH, size.width(), + CAMERA_IMGPROP_HEIGHT, size.height()); + } + + if (result != CAMERA_EOK) + qWarning() << "Unable to set resolution of viewfinder:" << result; + + } else if (parameter == QCameraViewfinderSettingsControl::MinimumFrameRate) { + camera_error_t result = CAMERA_EOK; + const double minimumFrameRate = value.toReal(); + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_set_photovf_property(m_session->handle(), CAMERA_IMGPROP_MINFRAMERATE, minimumFrameRate); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_set_videovf_property(m_session->handle(), CAMERA_IMGPROP_MINFRAMERATE, minimumFrameRate); + + if (result != CAMERA_EOK) + qWarning() << "Unable to set minimum framerate of viewfinder:" << result; + + } else if (parameter == QCameraViewfinderSettingsControl::MaximumFrameRate) { + camera_error_t result = CAMERA_EOK; + const double maximumFrameRate = value.toReal(); + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_set_photovf_property(m_session->handle(), CAMERA_IMGPROP_FRAMERATE, maximumFrameRate); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_set_videovf_property(m_session->handle(), CAMERA_IMGPROP_FRAMERATE, maximumFrameRate); + + if (result != CAMERA_EOK) + qWarning() << "Unable to set maximum framerate of viewfinder:" << result; + + } else if (parameter == QCameraViewfinderSettingsControl::PixelFormat) { + camera_error_t result = CAMERA_EOK; + camera_frametype_t format = CAMERA_FRAMETYPE_UNSPECIFIED; + + switch (value.value<QVideoFrame::PixelFormat>()) { + case QVideoFrame::Format_NV12: + format = CAMERA_FRAMETYPE_NV12; + break; + case QVideoFrame::Format_ARGB32: + format = CAMERA_FRAMETYPE_RGB8888; + break; + case QVideoFrame::Format_RGB24: + format = CAMERA_FRAMETYPE_RGB888; + break; + case QVideoFrame::Format_Jpeg: + format = CAMERA_FRAMETYPE_JPEG; + break; + case QVideoFrame::Format_Y8: + format = CAMERA_FRAMETYPE_GRAY8; + break; + default: + format = CAMERA_FRAMETYPE_UNSPECIFIED; + break; + } + + if (m_session->captureMode() & QCamera::CaptureStillImage) + result = camera_set_photovf_property(m_session->handle(), CAMERA_IMGPROP_FORMAT, format); + else if (m_session->captureMode() & QCamera::CaptureVideo) + result = camera_set_videovf_property(m_session->handle(), CAMERA_IMGPROP_FORMAT, format); + + if (result != CAMERA_EOK) + qWarning() << "Unable to set pixel format of viewfinder:" << result; + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol_p.h b/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol_p.h new file mode 100644 index 000000000..a41ff3001 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbcameraviewfindersettingscontrol_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBCAMERAVIEWVINDERSETTINGSCONTROL_H +#define BBCAMERAVIEWVINDERSETTINGSCONTROL_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 <qcameraviewfindersettingscontrol.h> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbCameraViewfinderSettingsControl : public QCameraViewfinderSettingsControl +{ + Q_OBJECT +public: + explicit BbCameraViewfinderSettingsControl(BbCameraSession *session, QObject *parent = 0); + + bool isViewfinderParameterSupported(ViewfinderParameter parameter) const override; + QVariant viewfinderParameter(ViewfinderParameter parameter) const override; + void setViewfinderParameter(ViewfinderParameter parameter, const QVariant &value) override; + +private: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbimageencodercontrol.cpp b/src/multimedia/platform/qnx/camera/bbimageencodercontrol.cpp new file mode 100644 index 000000000..960accb02 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbimageencodercontrol.cpp @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbimageencodercontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbImageEncoderControl::BbImageEncoderControl(BbCameraSession *session, QObject *parent) + : QImageEncoderControl(parent) + , m_session(session) +{ +} + +QStringList BbImageEncoderControl::supportedImageCodecs() const +{ + return QStringList() << QLatin1String("jpeg"); +} + +QString BbImageEncoderControl::imageCodecDescription(const QString &codecName) const +{ + if (codecName == QLatin1String("jpeg")) + return tr("JPEG image"); + + return QString(); +} + +QList<QSize> BbImageEncoderControl::supportedResolutions(const QImageEncoderSettings &settings, bool *continuous) const +{ + return m_session->supportedResolutions(settings, continuous); +} + +QImageEncoderSettings BbImageEncoderControl::imageSettings() const +{ + return m_session->imageSettings(); +} + +void BbImageEncoderControl::setImageSettings(const QImageEncoderSettings &settings) +{ + m_session->setImageSettings(settings); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbimageencodercontrol_p.h b/src/multimedia/platform/qnx/camera/bbimageencodercontrol_p.h new file mode 100644 index 000000000..2ace35b4f --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbimageencodercontrol_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBIMAGEENCODERCONTROL_H +#define BBIMAGEENCODERCONTROL_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 BbCameraSession; + +class BbImageEncoderControl : public QImageEncoderControl +{ + Q_OBJECT +public: + explicit BbImageEncoderControl(BbCameraSession *session, QObject *parent = 0); + + 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: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbmediastoragelocation.cpp b/src/multimedia/platform/qnx/camera/bbmediastoragelocation.cpp new file mode 100644 index 000000000..3b8eac160 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbmediastoragelocation.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbmediastoragelocation_p.h" + +#include <QStandardPaths> + +QT_BEGIN_NAMESPACE + +BbMediaStorageLocation::BbMediaStorageLocation() +{ +} + +QDir BbMediaStorageLocation::defaultDir(QCamera::CaptureMode mode) const +{ + QStringList dirCandidates; + + dirCandidates << QLatin1String("shared/camera"); + + if (mode == QCamera::CaptureVideo) { + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::MoviesLocation); + } else { + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + } + + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + for (const QString &path : qAsConst(dirCandidates)) { + if (QFileInfo(path).isWritable()) + return QDir(path); + } + + return QDir(); +} + +QString BbMediaStorageLocation::generateFileName(const QString &requestedName, QCamera::CaptureMode mode, const QString &prefix, const QString &extension) const +{ + if (requestedName.isEmpty()) + return generateFileName(prefix, defaultDir(mode), extension); + + if (QFileInfo(requestedName).isDir()) + return generateFileName(prefix, QDir(requestedName), extension); + + return requestedName; +} + +QString BbMediaStorageLocation::generateFileName(const QString &prefix, const QDir &dir, const QString &extension) const +{ + const QString lastMediaKey = dir.absolutePath() + QLatin1Char(' ') + prefix + QLatin1Char(' ') + extension; + qint64 lastMediaIndex = m_lastUsedIndex.value(lastMediaKey, 0); + + if (lastMediaIndex == 0) { + // first run, find the maximum media number during the fist capture + const auto list = dir.entryList(QStringList() << QString("%1*.%2").arg(prefix).arg(extension)); + for (const QString &fileName : list) { + const qint64 mediaIndex = QStringView{fileName}.mid(prefix.length(), fileName.size() - prefix.length() - extension.length() - 1).toInt(); + lastMediaIndex = qMax(lastMediaIndex, mediaIndex); + } + } + + // don't just rely on cached lastMediaIndex value, + // someone else may create a file after camera started + while (true) { + const QString name = QString("%1%2.%3").arg(prefix) + .arg(lastMediaIndex + 1, 8, 10, QLatin1Char('0')) + .arg(extension); + + const QString path = dir.absoluteFilePath(name); + if (!QFileInfo(path).exists()) { + m_lastUsedIndex[lastMediaKey] = lastMediaIndex + 1; + return path; + } + + lastMediaIndex++; + } + + return QString(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbmediastoragelocation_p.h b/src/multimedia/platform/qnx/camera/bbmediastoragelocation_p.h new file mode 100644 index 000000000..3ea1bfbf0 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbmediastoragelocation_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBMEDIASTORAGELOCATION_H +#define BBMEDIASTORAGELOCATION_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> +#include <QDir> +#include <QHash> + +QT_BEGIN_NAMESPACE + +class BbMediaStorageLocation +{ +public: + BbMediaStorageLocation(); + + QDir defaultDir(QCamera::CaptureMode mode) const; + + QString generateFileName(const QString &requestedName, QCamera::CaptureMode mode, const QString &prefix, const QString &extension) const; + QString generateFileName(const QString &prefix, const QDir &dir, const QString &extension) const; + +private: + mutable QHash<QString, qint64> m_lastUsedIndex; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol.cpp b/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol.cpp new file mode 100644 index 000000000..61c6d87f8 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol.cpp @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbvideodeviceselectorcontrol_p.h" + +#include "bbcamerasession_p.h" + +#include <QDebug> + +QT_BEGIN_NAMESPACE + +BbVideoDeviceSelectorControl::BbVideoDeviceSelectorControl(BbCameraSession *session, QObject *parent) + : QVideoDeviceSelectorControl(parent) + , m_session(session) + , m_default(0) + , m_selected(0) +{ + enumerateDevices(&m_devices, &m_descriptions); + + // pre-select the rear camera + const int index = m_devices.indexOf(BbCameraSession::cameraIdentifierRear()); + if (index != -1) + m_default = m_selected = index; +} + +int BbVideoDeviceSelectorControl::deviceCount() const +{ + return m_devices.count(); +} + +QString BbVideoDeviceSelectorControl::deviceName(int index) const +{ + if (index < 0 || index >= m_devices.count()) + return QString(); + + return QString::fromUtf8(m_devices.at(index)); +} + +QString BbVideoDeviceSelectorControl::deviceDescription(int index) const +{ + if (index < 0 || index >= m_descriptions.count()) + return QString(); + + return m_descriptions.at(index); +} + +QCamera::Position BbVideoDeviceSelectorControl::cameraPosition(int index) const +{ + if (deviceName(index) == QString::fromUtf8(BbCameraSession::cameraIdentifierFront())) + return QCamera::FrontFace; + else if (deviceName(index) == QString::fromUtf8(BbCameraSession::cameraIdentifierRear())) + return QCamera::BackFace; + else + return QCamera::UnspecifiedPosition; +} + +int BbVideoDeviceSelectorControl::cameraOrientation(int index) const +{ + return 0; +} + +int BbVideoDeviceSelectorControl::defaultDevice() const +{ + return m_default; +} + +int BbVideoDeviceSelectorControl::selectedDevice() const +{ + return m_selected; +} + +void BbVideoDeviceSelectorControl::enumerateDevices(QList<QByteArray> *devices, QStringList *descriptions) +{ + devices->clear(); + descriptions->clear(); + + camera_unit_t cameras[10]; + + unsigned int knownCameras = 0; + const camera_error_t result = camera_get_supported_cameras(10, &knownCameras, cameras); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve supported camera types:" << result; + return; + } + + for (unsigned int i = 0; i < knownCameras; ++i) { + switch (cameras[i]) { + case CAMERA_UNIT_FRONT: + devices->append(BbCameraSession::cameraIdentifierFront()); + descriptions->append(tr("Front Camera")); + break; + case CAMERA_UNIT_REAR: + devices->append(BbCameraSession::cameraIdentifierRear()); + descriptions->append(tr("Rear Camera")); + break; + case CAMERA_UNIT_DESKTOP: + devices->append(BbCameraSession::cameraIdentifierDesktop()); + descriptions->append(tr("Desktop Camera")); + break; + default: + break; + } + } +} + +void BbVideoDeviceSelectorControl::setSelectedDevice(int index) +{ + if (index < 0 || index >= m_devices.count()) + return; + + if (!m_session) + return; + + const QByteArray device = m_devices.at(index); + if (device == m_session->device()) + return; + + m_session->setDevice(device); + m_selected = index; + + emit selectedDeviceChanged(QString::fromUtf8(device)); + emit selectedDeviceChanged(index); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol_p.h b/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol_p.h new file mode 100644 index 000000000..36e6b2515 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbvideodeviceselectorcontrol_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBVIDEODEVICESELECTORCONTROL_H +#define BBVIDEODEVICESELECTORCONTROL_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 <QStringList> + +QT_BEGIN_NAMESPACE + +class BbCameraSession; + +class BbVideoDeviceSelectorControl : public QVideoDeviceSelectorControl +{ + Q_OBJECT +public: + explicit BbVideoDeviceSelectorControl(BbCameraSession *session, QObject *parent = 0); + + int deviceCount() const override; + QString deviceName(int index) const override; + QString deviceDescription(int index) const override; + QCamera::Position cameraPosition(int index) const; + int cameraOrientation(int index) const; + + int defaultDevice() const override; + int selectedDevice() const override; + + static void enumerateDevices(QList<QByteArray> *devices, QStringList *descriptions); + +public Q_SLOTS: + void setSelectedDevice(int index) override; + +private: + BbCameraSession* m_session; + + QList<QByteArray> m_devices; + QStringList m_descriptions; + + int m_default; + int m_selected; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/bbvideorenderercontrol.cpp b/src/multimedia/platform/qnx/camera/bbvideorenderercontrol.cpp new file mode 100644 index 000000000..58739a1ed --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbvideorenderercontrol.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "bbvideorenderercontrol_p.h" + +#include "bbcamerasession_p.h" + +QT_BEGIN_NAMESPACE + +BbVideoRendererControl::BbVideoRendererControl(BbCameraSession *session, QObject *parent) + : QVideoRendererControl(parent) + , m_session(session) +{ +} + +QAbstractVideoSurface* BbVideoRendererControl::surface() const +{ + return m_session->surface(); +} + +void BbVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + m_session->setSurface(surface); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/camera/bbvideorenderercontrol_p.h b/src/multimedia/platform/qnx/camera/bbvideorenderercontrol_p.h new file mode 100644 index 000000000..95448077b --- /dev/null +++ b/src/multimedia/platform/qnx/camera/bbvideorenderercontrol_p.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 BBVIDEORENDERERCONTROL_H +#define BBVIDEORENDERERCONTROL_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 BbCameraSession; + +class BbVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + explicit BbVideoRendererControl(BbCameraSession *session, QObject *parent = 0); + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + +private: + BbCameraSession *m_session; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/camera/camera.pri b/src/multimedia/platform/qnx/camera/camera.pri new file mode 100644 index 000000000..02cee13d3 --- /dev/null +++ b/src/multimedia/platform/qnx/camera/camera.pri @@ -0,0 +1,40 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/bbcameraaudioencodersettingscontrol_p.h \ + $$PWD/bbcameracontrol_p.h \ + $$PWD/bbcameraexposurecontrol_p.h \ + $$PWD/bbcamerafocuscontrol_p.h \ + $$PWD/bbcameraimagecapturecontrol_p.h \ + $$PWD/bbcameraimageprocessingcontrol_p.h \ + $$PWD/bbcameramediarecordercontrol_p.h \ + $$PWD/bbcameraorientatio_p.handler.h \ + $$PWD/bbcameraservice_p.h \ + $$PWD/bbcamerasession_p.h \ + $$PWD/bbcameravideoencodersettingscontrol_p.h \ + $$PWD/bbcameraviewfindersettingscontrol_p.h \ + $$PWD/bbimageencodercontrol_p.h \ + $$PWD/bbmediastoragelocation_p.h \ + $$PWD/bbvideodeviceselectorcontrol_p.h \ + $$PWD/bbvideorenderercontrol_p.h + +SOURCES += \ + $$PWD/bbcameraaudioencodersettingscontrol.cpp \ + $$PWD/bbcameracontrol.cpp \ + $$PWD/bbcameraexposurecontrol.cpp \ + $$PWD/bbcamerafocuscontrol.cpp \ + $$PWD/bbcameraimagecapturecontrol.cpp \ + $$PWD/bbcameraimageprocessingcontrol.cpp \ + $$PWD/bbcameramediarecordercontrol.cpp \ + $$PWD/bbcameraorientatio_p.handler.cpp \ + $$PWD/bbcameraservice.cpp \ + $$PWD/bbcamerasession.cpp \ + $$PWD/bbcameravideoencodersettingscontrol.cpp \ + $$PWD/bbcameraviewfindersettingscontrol.cpp \ + $$PWD/bbimageencodercontrol.cpp \ + $$PWD/bbmediastoragelocation.cpp \ + $$PWD/bbvideodeviceselectorcontrol.cpp \ + $$PWD/bbvideorenderercontrol.cpp + +LIBS += -lcamapi -laudio_manager + diff --git a/src/multimedia/platform/qnx/common/common.pri b/src/multimedia/platform/qnx/common/common.pri new file mode 100644 index 000000000..dc37a291d --- /dev/null +++ b/src/multimedia/platform/qnx/common/common.pri @@ -0,0 +1,7 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/windowgrabber_p.h + +SOURCES += \ + $$PWD/windowgrabber.cpp diff --git a/src/multimedia/platform/qnx/common/windowgrabber.cpp b/src/multimedia/platform/qnx/common/windowgrabber.cpp new file mode 100644 index 000000000..e4c8c926d --- /dev/null +++ b/src/multimedia/platform/qnx/common/windowgrabber.cpp @@ -0,0 +1,419 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "windowgrabber_p.h" + +#include <QAbstractEventDispatcher> +#include <QDebug> +#include <QGuiApplication> +#include <QImage> +#include <QThread> +#include <qpa/qplatformnativeinterface.h> + +#include <QOpenGLContext> +#include <QOpenGLFunctions> + +#include <errno.h> + +QT_BEGIN_NAMESPACE + +static PFNEGLCREATEIMAGEKHRPROC s_eglCreateImageKHR; +static PFNEGLDESTROYIMAGEKHRPROC s_eglDestroyImageKHR; + +WindowGrabber::WindowGrabber(QObject *parent) + : QObject(parent), + m_windowParent(nullptr), + m_screenContext(0), + m_active(false), + m_currentFrame(0), + m_eglImageSupported(false), + m_eglImageCheck(false) +{ + // grab the window frame with 60 frames per second + m_timer.setInterval(1000/60); + + connect(&m_timer, SIGNAL(timeout()), SLOT(triggerUpdate())); + + QCoreApplication::eventDispatcher()->installNativeEventFilter(this); + + for ( int i = 0; i < 2; ++i ) + m_images[i] = 0; + + // Use of EGL images can be disabled by setting QQNX_MM_DISABLE_EGLIMAGE_SUPPORT to something + // non-zero. This is probably useful only to test that this path still works since it results + // in a high CPU load. + if (!s_eglCreateImageKHR && qgetenv("QQNX_MM_DISABLE_EGLIMAGE_SUPPORT").toInt() == 0) { + s_eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR")); + s_eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR")); + } + + QPlatformNativeInterface *const nativeInterface = QGuiApplication::platformNativeInterface(); + if (nativeInterface) { + m_screenContext = static_cast<screen_context_t>( + nativeInterface->nativeResourceForIntegration("screenContext")); + } + + // Create a parent window for the window whose content will be grabbed. Since the + // window is only a buffer conduit, the characteristics of the parent window are + // irrelevant. The contents of the window can be grabbed so long as the window + // joins the parent window's group and the parent window is in this process. + // Using the window that displays this content isn't possible because there's no + // way to reliably retrieve it from this code or any calling code. + screen_create_window(&m_windowParent, m_screenContext); + screen_create_window_group(m_windowParent, nullptr); +} + +WindowGrabber::~WindowGrabber() +{ + screen_destroy_window(m_windowParent); + QCoreApplication::eventDispatcher()->removeNativeEventFilter(this); + cleanup(); +} + +void WindowGrabber::setFrameRate(int frameRate) +{ + m_timer.setInterval(1000/frameRate); +} + + +void WindowGrabber::setWindowId(const QByteArray &windowId) +{ + m_windowId = windowId; +} + +void WindowGrabber::start() +{ + if (m_active) + return; + + if (!m_screenContext) + screen_get_window_property_pv(m_window, SCREEN_PROPERTY_CONTEXT, reinterpret_cast<void**>(&m_screenContext)); + + m_timer.start(); + + m_active = true; +} + +void WindowGrabber::stop() +{ + if (!m_active) + return; + + cleanup(); + + m_timer.stop(); + + m_active = false; +} + +void WindowGrabber::pause() +{ + m_timer.stop(); +} + +void WindowGrabber::resume() +{ + if (!m_active) + return; + + m_timer.start(); +} + +bool WindowGrabber::handleScreenEvent(screen_event_t screen_event) +{ + + int eventType; + if (screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType) != 0) { + qWarning() << "WindowGrabber: Failed to query screen event type"; + return false; + } + + if (eventType != SCREEN_EVENT_CREATE) + return false; + + screen_window_t window = 0; + if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&window) != 0) { + qWarning() << "WindowGrabber: Failed to query window property"; + return false; + } + + const int maxIdStrLength = 128; + char idString[maxIdStrLength]; + if (screen_get_window_property_cv(window, SCREEN_PROPERTY_ID_STRING, maxIdStrLength, idString) != 0) { + qWarning() << "WindowGrabber: Failed to query window ID string"; + return false; + } + + // Grab windows that have a non-empty ID string and a matching window id to grab + if (idString[0] != '\0' && m_windowId == idString) { + m_window = window; + start(); + } + + return false; +} + +bool WindowGrabber::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) +{ + if (eventType == "screen_event_t") { + const screen_event_t event = static_cast<screen_event_t>(message); + return handleScreenEvent(event); + } + + return false; +} + +QByteArray WindowGrabber::windowGroupId() const +{ + char groupName[256]; + memset(groupName, 0, sizeof(groupName)); + screen_get_window_property_cv(m_windowParent, + SCREEN_PROPERTY_GROUP, + sizeof(groupName) - 1, + groupName); + return QByteArray(groupName); +} + +bool WindowGrabber::eglImageSupported() +{ + return m_eglImageSupported; +} + +void WindowGrabber::checkForEglImageExtension() +{ + QOpenGLContext *m_context = QOpenGLContext::currentContext(); + if (!m_context) //Should not happen, because we are called from the render thread + return; + + QByteArray eglExtensions = QByteArray(eglQueryString(eglGetDisplay(EGL_DEFAULT_DISPLAY), + EGL_EXTENSIONS)); + m_eglImageSupported = m_context->hasExtension(QByteArrayLiteral("GL_OES_EGL_image")) + && eglExtensions.contains(QByteArrayLiteral("EGL_KHR_image")) + && s_eglCreateImageKHR && s_eglDestroyImageKHR; + + if (strstr(reinterpret_cast<const char*>(glGetString(GL_VENDOR)), "VMware")) + m_eglImageSupported = false; + + m_eglImageCheck = true; +} + +void WindowGrabber::triggerUpdate() +{ + if (!m_eglImageCheck) // We did not check for egl images yet + return; + + int size[2] = { 0, 0 }; + + int result = screen_get_window_property_iv(m_window, SCREEN_PROPERTY_SOURCE_SIZE, size); + if (result != 0) { + cleanup(); + qWarning() << "WindowGrabber: cannot get window size:" << strerror(errno); + return; + } + + if (m_size.width() != size[0] || m_size.height() != size[1]) + m_size = QSize(size[0], size[1]); + + emit updateScene(m_size); +} + +bool WindowGrabber::selectBuffer() +{ + // If we're using egl images we need to double buffer since the gpu may still be using the last + // video frame. If we're not, it doesn't matter since the data is immediately copied. + if (eglImageSupported()) + m_currentFrame = (m_currentFrame + 1) % 2; + + if (!m_images[m_currentFrame]) { + m_images[m_currentFrame] = new WindowGrabberImage(); + if (!m_images[m_currentFrame]->initialize(m_screenContext)) { + delete m_images[m_currentFrame]; + m_images[m_currentFrame] = 0; + return false; + } + } + return true; +} + +int WindowGrabber::getNextTextureId() +{ + if (!selectBuffer()) + return 0; + return m_images[m_currentFrame]->getTexture(m_window, m_size); +} + +QImage WindowGrabber::getNextImage() +{ + if (!selectBuffer()) + return QImage(); + + return m_images[m_currentFrame]->getImage(m_window, m_size); +} + +void WindowGrabber::cleanup() +{ + for ( int i = 0; i < 2; ++i ) { + if (m_images[i]) { + m_images[i]->destroy(); + m_images[i] = 0; + } + } +} + + +WindowGrabberImage::WindowGrabberImage() + : m_pixmap(0), m_pixmapBuffer(0), m_eglImage(0), m_glTexture(0), m_bufferAddress(0), m_bufferStride(0) +{ +} + +WindowGrabberImage::~WindowGrabberImage() +{ + if (m_glTexture) + glDeleteTextures(1, &m_glTexture); + if (m_eglImage) + s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage); + if (m_pixmap) + screen_destroy_pixmap(m_pixmap); +} + +bool +WindowGrabberImage::initialize(screen_context_t screenContext) +{ + if (screen_create_pixmap(&m_pixmap, screenContext) != 0) { + qWarning() << "WindowGrabber: cannot create pixmap:" << strerror(errno); + return false; + } + const int usage = SCREEN_USAGE_WRITE | SCREEN_USAGE_READ | SCREEN_USAGE_NATIVE; + screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_USAGE, &usage); + + const int format = SCREEN_FORMAT_RGBX8888; + screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_FORMAT, &format); + + return true; +} + +void +WindowGrabberImage::destroy() +{ + // We want to delete in the thread we were created in since we need the thread that + // has called eglMakeCurrent on the right EGL context. This doesn't actually guarantee + // this but that would be hard to achieve and in practice it works. + if (QThread::currentThread() == thread()) + delete this; + else + deleteLater(); +} + +bool +WindowGrabberImage::resize(const QSize &newSize) +{ + if (m_pixmapBuffer) { + screen_destroy_pixmap_buffer(m_pixmap); + m_pixmapBuffer = 0; + m_bufferAddress = 0; + m_bufferStride = 0; + } + + int size[2] = { newSize.width(), newSize.height() }; + + screen_set_pixmap_property_iv(m_pixmap, SCREEN_PROPERTY_BUFFER_SIZE, size); + + if (screen_create_pixmap_buffer(m_pixmap) == 0) { + screen_get_pixmap_property_pv(m_pixmap, SCREEN_PROPERTY_RENDER_BUFFERS, + reinterpret_cast<void**>(&m_pixmapBuffer)); + screen_get_buffer_property_pv(m_pixmapBuffer, SCREEN_PROPERTY_POINTER, + reinterpret_cast<void**>(&m_bufferAddress)); + screen_get_buffer_property_iv(m_pixmapBuffer, SCREEN_PROPERTY_STRIDE, &m_bufferStride); + m_size = newSize; + + return true; + } else { + m_size = QSize(); + return false; + } +} + +bool +WindowGrabberImage::grab(screen_window_t window) +{ + const int rect[] = { 0, 0, m_size.width(), m_size.height() }; + return screen_read_window(window, m_pixmapBuffer, 1, rect, 0) == 0; +} + +QImage +WindowGrabberImage::getImage(screen_window_t window, const QSize &size) +{ + if (size != m_size) { + if (!resize(size)) + return QImage(); + } + if (!m_bufferAddress || !grab(window)) + return QImage(); + + return QImage(m_bufferAddress, m_size.width(), m_size.height(), m_bufferStride, QImage::Format_ARGB32); +} + +GLuint +WindowGrabberImage::getTexture(screen_window_t window, const QSize &size) +{ + if (size != m_size) { + if (!m_glTexture) + glGenTextures(1, &m_glTexture); + glBindTexture(GL_TEXTURE_2D, m_glTexture); + if (m_eglImage) { + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, 0); + s_eglDestroyImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), m_eglImage); + m_eglImage = 0; + } + if (!resize(size)) + return 0; + m_eglImage = s_eglCreateImageKHR(eglGetDisplay(EGL_DEFAULT_DISPLAY), EGL_NO_CONTEXT, + EGL_NATIVE_PIXMAP_KHR, m_pixmap, 0); + glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, m_eglImage); + } + + if (!m_pixmap || !grab(window)) + return 0; + + return m_glTexture; +} + + + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/common/windowgrabber_p.h b/src/multimedia/platform/qnx/common/windowgrabber_p.h new file mode 100644 index 000000000..79a234b2d --- /dev/null +++ b/src/multimedia/platform/qnx/common/windowgrabber_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 WINDOWGRABBER_H +#define WINDOWGRABBER_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. +// + +#define EGL_EGLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#include <EGL/egl.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <EGL/eglext.h> +#include <QAbstractNativeEventFilter> +#include <QObject> +#include <QSize> +#include <QTimer> + +#include <screen/screen.h> + +QT_BEGIN_NAMESPACE + +class WindowGrabberImage : public QObject +{ + Q_OBJECT + +public: + WindowGrabberImage(); + ~WindowGrabberImage(); + + bool initialize(screen_context_t screenContext); + + void destroy(); + + QImage getImage(screen_window_t window, const QSize &size); + GLuint getTexture(screen_window_t window, const QSize &size); + +private: + bool grab(screen_window_t window); + bool resize(const QSize &size); + + QSize m_size; + screen_pixmap_t m_pixmap; + screen_buffer_t m_pixmapBuffer; + EGLImageKHR m_eglImage; + GLuint m_glTexture; + unsigned char *m_bufferAddress; + int m_bufferStride; +}; + +class WindowGrabber : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT + +public: + explicit WindowGrabber(QObject *parent = 0); + ~WindowGrabber(); + + void setFrameRate(int frameRate); + + void setWindowId(const QByteArray &windowId); + + void start(); + void stop(); + + void pause(); + void resume(); + + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; + + bool handleScreenEvent(screen_event_t event); + + QByteArray windowGroupId() const; + + bool eglImageSupported(); + void checkForEglImageExtension(); + + int getNextTextureId(); + QImage getNextImage(); + +signals: + void updateScene(const QSize &size); + +private slots: + void triggerUpdate(); + +private: + bool selectBuffer(); + void cleanup(); + + QTimer m_timer; + + QByteArray m_windowId; + + screen_window_t m_windowParent; + screen_window_t m_window; + screen_context_t m_screenContext; + + WindowGrabberImage *m_images[2]; + QSize m_size; + + bool m_active; + int m_currentFrame; + bool m_eglImageSupported; + bool m_eglImageCheck; // We must not send a grabed frame before this is true +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mediaplayer.pri b/src/multimedia/platform/qnx/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..a44790b7d --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mediaplayer.pri @@ -0,0 +1,24 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/mmrenderermediaplayercontrol_p.h \ + $$PWD/mmrenderermediaplayerservice_p.h \ + $$PWD/mmrenderermetadata_p.h \ + $$PWD/mmrenderermetadatareadercontrol_p.h \ + $$PWD/mmrendererplayervideorenderercontrol_p.h \ + $$PWD/mmrendererutil_p.h \ + $$PWD/mmrenderervideowindowcontrol_p.h \ + $$PWD/mmreventmediaplayercontrol_p.h \ + $$PWD/mmrevent_p.hread.h +SOURCES += \ + $$PWD/mmrenderermediaplayercontrol.cpp \ + $$PWD/mmrenderermediaplayerservice.cpp \ + $$PWD/mmrenderermetadata.cpp \ + $$PWD/mmrenderermetadatareadercontrol.cpp \ + $$PWD/mmrendererplayervideorenderercontrol.cpp \ + $$PWD/mmrendererutil.cpp \ + $$PWD/mmrenderervideowindowcontrol.cpp \ + $$PWD/mmreventmediaplayercontrol.cpp \ + $$PWD/mmrevent_p.hread.cpp + +QMAKE_USE += mmrenderer diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp new file mode 100644 index 000000000..c374d97b2 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp @@ -0,0 +1,664 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrendereraudiorolecontrol_p.h" +#include "mmrenderercustomaudiorolecontrol_p.h" +#include "mmrenderermediaplayercontrol_p.h" +#include "mmrenderermetadatareadercontrol_p.h" +#include "mmrendererplayervideorenderercontrol_p.h" +#include "mmrendererutil_p.h" +#include "mmrenderervideowindowcontrol_p.h" +#include <QtCore/qabstracteventdispatcher.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdir.h> +#include <QtCore/qfileinfo.h> +#include <QtCore/quuid.h> +#include <mm/renderer.h> + +#include <errno.h> +#include <sys/strm.h> +#include <sys/stat.h> + +QT_BEGIN_NAMESPACE + +static int idCounter = 0; + +MmRendererMediaPlayerControl::MmRendererMediaPlayerControl(QObject *parent) + : QMediaPlayerControl(parent), + m_context(0), + m_id(-1), + m_connection(0), + m_audioId(-1), + m_state(QMediaPlayer::StoppedState), + m_volume(100), + m_muted(false), + m_rate(1), + m_position(0), + m_mediaStatus(QMediaPlayer::NoMedia), + m_playAfterMediaLoaded(false), + m_inputAttached(false), + m_bufferLevel(0) +{ + m_loadingTimer.setSingleShot(true); + m_loadingTimer.setInterval(0); + connect(&m_loadingTimer, SIGNAL(timeout()), this, SLOT(continueLoadMedia())); + QCoreApplication::eventDispatcher()->installNativeEventFilter(this); +} + +void MmRendererMediaPlayerControl::destroy() +{ + stop(); + detach(); + closeConnection(); + QCoreApplication::eventDispatcher()->removeNativeEventFilter(this); +} + +void MmRendererMediaPlayerControl::openConnection() +{ + m_connection = mmr_connect(NULL); + if (!m_connection) { + emitPError("Unable to connect to the multimedia renderer"); + return; + } + + m_id = idCounter++; + m_contextName = QString("MmRendererMediaPlayerControl_%1_%2").arg(m_id) + .arg(QCoreApplication::applicationPid()); + m_context = mmr_context_create(m_connection, m_contextName.toLatin1(), + 0, S_IRWXU|S_IRWXG|S_IRWXO); + if (!m_context) { + emitPError("Unable to create context"); + closeConnection(); + return; + } + + startMonitoring(); +} + +void MmRendererMediaPlayerControl::handleMmStopped() +{ + // Only react to stop events that happen when the end of the stream is reached and + // playback is stopped because of this. + // Ignore other stop event sources, such as calling mmr_stop() ourselves. + if (m_state != QMediaPlayer::StoppedState) { + setMediaStatus(QMediaPlayer::EndOfMedia); + stopInternal(IgnoreMmRenderer); + } +} + +void MmRendererMediaPlayerControl::handleMmSuspend(const QString &reason) +{ + if (m_state == QMediaPlayer::StoppedState) + return; + + Q_UNUSED(reason); + setMediaStatus(QMediaPlayer::StalledMedia); +} + +void MmRendererMediaPlayerControl::handleMmSuspendRemoval(const QString &bufferStatus) +{ + if (m_state == QMediaPlayer::StoppedState) + return; + + if (bufferStatus == QLatin1String("buffering")) + setMediaStatus(QMediaPlayer::BufferingMedia); + else + setMediaStatus(QMediaPlayer::BufferedMedia); +} + +void MmRendererMediaPlayerControl::handleMmPause() +{ + if (m_state == QMediaPlayer::PlayingState) { + setState(QMediaPlayer::PausedState); + } +} + +void MmRendererMediaPlayerControl::handleMmPlay() +{ + if (m_state == QMediaPlayer::PausedState) { + setState(QMediaPlayer::PlayingState); + } +} + +void MmRendererMediaPlayerControl::closeConnection() +{ + stopMonitoring(); + + if (m_context) { + mmr_context_destroy(m_context); + m_context = 0; + m_contextName.clear(); + } + + if (m_connection) { + mmr_disconnect(m_connection); + m_connection = 0; + } +} + +QByteArray MmRendererMediaPlayerControl::resourcePathForUrl(const QUrl &url) +{ + // If this is a local file, mmrenderer expects the file:// prefix and an absolute path. + // We treat URLs without scheme as local files, most likely someone just forgot to set the + // file:// prefix when constructing the URL. + if (url.isLocalFile() || url.scheme().isEmpty()) { + QString relativeFilePath; + if (!url.scheme().isEmpty()) + relativeFilePath = url.toLocalFile(); + else + relativeFilePath = url.path(); + const QFileInfo fileInfo(relativeFilePath); + return QFile::encodeName(QStringLiteral("file://") + fileInfo.absoluteFilePath()); + + // HTTP or similar URL + } else { + return url.toEncoded(); + } +} + +void MmRendererMediaPlayerControl::attach() +{ + // Should only be called in detached state + Q_ASSERT(m_audioId == -1 && !m_inputAttached); + + if (m_media.isNull() || !m_context) { + setMediaStatus(QMediaPlayer::NoMedia); + return; + } + + resetMonitoring(); + + if (m_videoRendererControl) + m_videoRendererControl->attachDisplay(m_context); + + if (m_videoWindowControl) + m_videoWindowControl->attachDisplay(m_context); + + const QByteArray defaultAudioDevice = qgetenv("QQNX_RENDERER_DEFAULT_AUDIO_SINK"); + m_audioId = mmr_output_attach(m_context, defaultAudioDevice.isEmpty() ? "audio:default" : defaultAudioDevice.constData(), "audio"); + if (m_audioId == -1) { + emitMmError("mmr_output_attach() for audio failed"); + return; + } + + if (m_audioId != -1) { + QString audioType = m_role == QAudio::CustomRole + ? m_customRole + : qnxAudioType(m_role); + QByteArray latin1AudioType = audioType.toLatin1(); + if (!audioType.isEmpty() && latin1AudioType == audioType) { + strm_dict_t *dict = strm_dict_new(); + dict = strm_dict_set(dict, "audio_type", latin1AudioType.constData()); + if (mmr_output_parameters(m_context, m_audioId, dict) != 0) + emitMmError("mmr_output_parameters: Setting audio_type failed"); + } + } + + const QByteArray resourcePath = resourcePathForUrl(m_media.request().url()); + if (resourcePath.isEmpty()) { + detach(); + return; + } + + if (mmr_input_attach(m_context, resourcePath.constData(), "track") != 0) { + emitMmError(QStringLiteral("mmr_input_attach() failed for ") + QString(resourcePath)); + setMediaStatus(QMediaPlayer::InvalidMedia); + detach(); + return; + } + + m_inputAttached = true; + setMediaStatus(QMediaPlayer::LoadedMedia); + + // mm-renderer has buffer properties "status" and "level" + // QMediaPlayer's buffer status maps to mm-renderer's buffer level + m_bufferLevel = 0; + emit bufferStatusChanged(m_bufferLevel); +} + +void MmRendererMediaPlayerControl::detach() +{ + if (m_context) { + if (m_inputAttached) { + mmr_input_detach(m_context); + m_inputAttached = false; + } + if (m_videoRendererControl) + m_videoRendererControl->detachDisplay(); + if (m_videoWindowControl) + m_videoWindowControl->detachDisplay(); + if (m_audioId != -1 && m_context) { + mmr_output_detach(m_context, m_audioId); + m_audioId = -1; + } + } + + m_loadingTimer.stop(); +} + +QMediaPlayer::State MmRendererMediaPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus MmRendererMediaPlayerControl::mediaStatus() const +{ + return m_mediaStatus; +} + +qint64 MmRendererMediaPlayerControl::duration() const +{ + return m_metaData.duration(); +} + +qint64 MmRendererMediaPlayerControl::position() const +{ + return m_position; +} + +void MmRendererMediaPlayerControl::setPosition(qint64 position) +{ + if (m_position != position) { + m_position = position; + + // Don't update in stopped state, it would not have any effect. Instead, the position is + // updated in play(). + if (m_state != QMediaPlayer::StoppedState) + setPositionInternal(m_position); + + emit positionChanged(m_position); + } +} + +int MmRendererMediaPlayerControl::volume() const +{ + return m_volume; +} + +void MmRendererMediaPlayerControl::setVolumeInternal(int newVolume) +{ + if (!m_context) + return; + + newVolume = qBound(0, newVolume, 100); + if (m_audioId != -1) { + strm_dict_t * dict = strm_dict_new(); + dict = strm_dict_set(dict, "volume", QString::number(newVolume).toLatin1()); + if (mmr_output_parameters(m_context, m_audioId, dict) != 0) + emitMmError("mmr_output_parameters: Setting volume failed"); + } +} + +void MmRendererMediaPlayerControl::setPlaybackRateInternal(qreal rate) +{ + if (!m_context) + return; + + const int mmRate = rate * 1000; + if (mmr_speed_set(m_context, mmRate) != 0) + emitMmError("mmr_speed_set failed"); +} + +void MmRendererMediaPlayerControl::setPositionInternal(qint64 position) +{ + if (!m_context) + return; + + if (m_metaData.isSeekable()) { + if (mmr_seek(m_context, QString::number(position).toLatin1()) != 0) + emitMmError("Seeking failed"); + } +} + +void MmRendererMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status) +{ + if (m_mediaStatus != status) { + m_mediaStatus = status; + emit mediaStatusChanged(m_mediaStatus); + } +} + +void MmRendererMediaPlayerControl::setState(QMediaPlayer::State state) +{ + if (m_state != state) { + if (m_videoRendererControl) { + if (state == QMediaPlayer::PausedState || state == QMediaPlayer::StoppedState) { + m_videoRendererControl->pause(); + } else if ((state == QMediaPlayer::PlayingState) + && (m_state == QMediaPlayer::PausedState + || m_state == QMediaPlayer::StoppedState)) { + m_videoRendererControl->resume(); + } + } + + m_state = state; + emit stateChanged(m_state); + } +} + +void MmRendererMediaPlayerControl::stopInternal(StopCommand stopCommand) +{ + resetMonitoring(); + setPosition(0); + + if (m_state != QMediaPlayer::StoppedState) { + + if (stopCommand == StopMmRenderer) { + mmr_stop(m_context); + } + + setState(QMediaPlayer::StoppedState); + } +} + +void MmRendererMediaPlayerControl::setVolume(int volume) +{ + const int newVolume = qBound(0, volume, 100); + if (m_volume != newVolume) { + m_volume = newVolume; + if (!m_muted) + setVolumeInternal(m_volume); + emit volumeChanged(m_volume); + } +} + +bool MmRendererMediaPlayerControl::isMuted() const +{ + return m_muted; +} + +void MmRendererMediaPlayerControl::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + setVolumeInternal(muted ? 0 : m_volume); + emit mutedChanged(muted); + } +} + +int MmRendererMediaPlayerControl::bufferStatus() const +{ + // mm-renderer has buffer properties "status" and "level" + // QMediaPlayer's buffer status maps to mm-renderer's buffer level + return m_bufferLevel; +} + +bool MmRendererMediaPlayerControl::isAudioAvailable() const +{ + return m_metaData.hasAudio(); +} + +bool MmRendererMediaPlayerControl::isVideoAvailable() const +{ + return m_metaData.hasVideo(); +} + +bool MmRendererMediaPlayerControl::isSeekable() const +{ + return m_metaData.isSeekable(); +} + +QMediaTimeRange MmRendererMediaPlayerControl::availablePlaybackRanges() const +{ + // We can't get this information from the mmrenderer API yet, so pretend we can seek everywhere + return QMediaTimeRange(0, m_metaData.duration()); +} + +qreal MmRendererMediaPlayerControl::playbackRate() const +{ + return m_rate; +} + +void MmRendererMediaPlayerControl::setPlaybackRate(qreal rate) +{ + if (m_rate != rate) { + m_rate = rate; + setPlaybackRateInternal(m_rate); + emit playbackRateChanged(m_rate); + } +} + +QUrl MmRendererMediaPlayerControl::media() const +{ + return m_media; +} + +const QIODevice *MmRendererMediaPlayerControl::mediaStream() const +{ + // Always 0, we don't support QIODevice streams + return 0; +} + +void MmRendererMediaPlayerControl::setMedia(const QUrl &media, QIODevice *stream) +{ + Q_UNUSED(stream); // not supported + + stop(); + detach(); + + m_media = media; + emit mediaChanged(m_media); + + // Slight hack: With MediaPlayer QtQuick elements that have autoPlay set to true, playback + // would start before the QtQuick canvas is propagated to all elements, and therefore our + // video output would not work. Therefore, delay actually playing the media a bit so that the + // canvas is ready. + // The mmrenderer doesn't allow to attach video outputs after playing has started, otherwise + // this would be unnecessary. + if (!m_media.isNull()) { + setMediaStatus(QMediaPlayer::LoadingMedia); + m_loadingTimer.start(); // singleshot timer to continueLoadMedia() + } else { + continueLoadMedia(); // still needed, as it will update the media status and clear metadata + } +} + +void MmRendererMediaPlayerControl::continueLoadMedia() +{ + updateMetaData(nullptr); + attach(); + if (m_playAfterMediaLoaded) + play(); +} + +MmRendererVideoWindowControl *MmRendererMediaPlayerControl::videoWindowControl() const +{ + return m_videoWindowControl; +} + +void MmRendererMediaPlayerControl::play() +{ + if (m_playAfterMediaLoaded) + m_playAfterMediaLoaded = false; + + // No-op if we are already playing, except if we were called from continueLoadMedia(), in which + // case m_playAfterMediaLoaded is true (hence the 'else'). + else if (m_state == QMediaPlayer::PlayingState) + return; + + if (m_mediaStatus == QMediaPlayer::LoadingMedia) { + + // State changes are supposed to be synchronous + setState(QMediaPlayer::PlayingState); + + // Defer playing to later, when the timer triggers continueLoadMedia() + m_playAfterMediaLoaded = true; + return; + } + + // Un-pause the state when it is paused + if (m_state == QMediaPlayer::PausedState) { + setPlaybackRateInternal(m_rate); + setState(QMediaPlayer::PlayingState); + return; + } + + if (m_media.isNull() || !m_connection || !m_context || m_audioId == -1) { + setState(QMediaPlayer::StoppedState); + return; + } + + if (m_mediaStatus == QMediaPlayer::EndOfMedia) + m_position = 0; + + resetMonitoring(); + setPositionInternal(m_position); + setVolumeInternal(m_muted ? 0 : m_volume); + setPlaybackRateInternal(m_rate); + + if (mmr_play(m_context) != 0) { + setState(QMediaPlayer::StoppedState); + emitMmError("mmr_play() failed"); + return; + } + + setState( QMediaPlayer::PlayingState); +} + +void MmRendererMediaPlayerControl::pause() +{ + if (m_state == QMediaPlayer::PlayingState) { + setPlaybackRateInternal(0); + setState(QMediaPlayer::PausedState); + } +} + +void MmRendererMediaPlayerControl::stop() +{ + stopInternal(StopMmRenderer); +} + +void MmRendererMediaPlayerControl::setAudioRole(QAudio::Role role) +{ + m_role = role; + m_customRole.clear(); +} + +QList<QAudio::Role> MmRendererMediaPlayerControl::supportedAudioRoles() const +{ + return qnxSupportedAudioRoles(); +} + +void MmRendererMediaPlayerControl::setCustomAudioRole(const QString &role) +{ + m_role = QAudio::CustomRole; + m_customRole = role; +} + +QStringList MmRendererMediaPlayerControl::supportedCustomAudioRoles() const +{ + return QStringList(); +} + +MmRendererPlayerVideoRendererControl *MmRendererMediaPlayerControl::videoRendererControl() const +{ + return m_videoRendererControl; +} + +void MmRendererMediaPlayerControl::setVideoRendererControl(MmRendererPlayerVideoRendererControl *videoControl) +{ + m_videoRendererControl = videoControl; +} + +void MmRendererMediaPlayerControl::setVideoWindowControl(MmRendererVideoWindowControl *videoControl) +{ + m_videoWindowControl = videoControl; +} + +void MmRendererMediaPlayerControl::setMetaDataReaderControl(MmRendererMetaDataReaderControl *metaDataReaderControl) +{ + m_metaDataReaderControl = metaDataReaderControl; +} + +void MmRendererMediaPlayerControl::setMmPosition(qint64 newPosition) +{ + if (newPosition != 0 && newPosition != m_position) { + m_position = newPosition; + emit positionChanged(m_position); + } +} + +void MmRendererMediaPlayerControl::setMmBufferStatus(const QString &bufferStatus) +{ + if (bufferStatus == QLatin1String("buffering")) + setMediaStatus(QMediaPlayer::BufferingMedia); + else if (bufferStatus == QLatin1String("playing")) + setMediaStatus(QMediaPlayer::BufferedMedia); + // ignore "idle" buffer status +} + +void MmRendererMediaPlayerControl::setMmBufferLevel(int level, int capacity) +{ + m_bufferLevel = capacity == 0 ? 0 : level / static_cast<float>(capacity) * 100.0f; + m_bufferLevel = qBound(0, m_bufferLevel, 100); + emit bufferStatusChanged(m_bufferLevel); +} + +void MmRendererMediaPlayerControl::updateMetaData(const strm_dict *dict) +{ + m_metaData.update(dict); + + if (m_videoWindowControl) + m_videoWindowControl->setMetaData(m_metaData); + + if (m_metaDataReaderControl) + m_metaDataReaderControl->setMetaData(m_metaData); + + emit durationChanged(m_metaData.duration()); + emit audioAvailableChanged(m_metaData.hasAudio()); + emit videoAvailableChanged(m_metaData.hasVideo()); + emit availablePlaybackRangesChanged(availablePlaybackRanges()); + emit seekableChanged(m_metaData.isSeekable()); +} + +void MmRendererMediaPlayerControl::emitMmError(const QString &msg) +{ + int errorCode = MMR_ERROR_NONE; + const QString errorMessage = mmErrorMessage(msg, m_context, &errorCode); + qDebug() << errorMessage; + emit error(errorCode, errorMessage); +} + +void MmRendererMediaPlayerControl::emitPError(const QString &msg) +{ + const QString errorMessage = QString("%1: %2").arg(msg).arg(strerror(errno)); + qDebug() << errorMessage; + emit error(errno, errorMessage); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol_p.h new file mode 100644 index 000000000..d6a75c597 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayercontrol_p.h @@ -0,0 +1,193 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERMEDIAPLAYERCONTROL_H +#define MMRENDERERMEDIAPLAYERCONTROL_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 "mmrenderermetadata_p.h" +#include <qmediaplayercontrol.h> +#include <QtCore/qabstractnativeeventfilter.h> +#include <QtCore/qpointer.h> +#include <QtCore/qtimer.h> + +typedef struct mmr_connection mmr_connection_t; +typedef struct mmr_context mmr_context_t; +typedef struct mmrenderer_monitor mmrenderer_monitor_t; +typedef struct strm_dict strm_dict_t; + +QT_BEGIN_NAMESPACE + +class MmRendererAudioRoleControl; +class MmRendererCustomAudioRoleControl; +class MmRendererMetaDataReaderControl; +class MmRendererPlayerVideoRendererControl; +class MmRendererVideoWindowControl; + +class MmRendererMediaPlayerControl : public QMediaPlayerControl, public QAbstractNativeEventFilter +{ + Q_OBJECT +public: + explicit MmRendererMediaPlayerControl(QObject *parent = 0); + + QMediaPlayer::State state() const override; + + QMediaPlayer::MediaStatus mediaStatus() const override; + + qint64 duration() const override; + + qint64 position() const override; + void setPosition(qint64 position) override; + + int volume() const override; + void setVolume(int volume) override; + + bool isMuted() const override; + void setMuted(bool muted) 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 &media, QIODevice *stream) override; + + void play() override; + void pause() override; + void stop() override; + + void setAudioRole(QAudio::Role role) override; + QList<QAudio::Role> supportedAudioRoles() const override; + + void setCustomAudioRole(const QString &role) override; + QStringList supportedCustomAudioRoles() const override; + + MmRendererPlayerVideoRendererControl *videoRendererControl() const; + void setVideoRendererControl(MmRendererPlayerVideoRendererControl *videoControl); + + MmRendererVideoWindowControl *videoWindowControl() const; + void setVideoWindowControl(MmRendererVideoWindowControl *videoControl); + void setMetaDataReaderControl(MmRendererMetaDataReaderControl *metaDataReaderControl); + +protected: + virtual void startMonitoring() = 0; + virtual void stopMonitoring() = 0; + virtual void resetMonitoring() = 0; + + void openConnection(); + void emitMmError(const QString &msg); + void emitPError(const QString &msg); + void setMmPosition(qint64 newPosition); + void setMmBufferStatus(const QString &bufferStatus); + void setMmBufferLevel(int level, int capacity); + void handleMmStopped(); + void handleMmSuspend(const QString &reason); + void handleMmSuspendRemoval(const QString &bufferStatus); + void handleMmPause(); + void handleMmPlay(); + void updateMetaData(const strm_dict_t *dict); + + // must be called from subclass dtors (calls virtual function stopMonitoring()) + void destroy(); + + mmr_context_t *m_context; + int m_id; + QString m_contextName; + +private Q_SLOTS: + void continueLoadMedia(); + +private: + QByteArray resourcePathForUrl(const QUrl &url); + void closeConnection(); + void attach(); + void detach(); + + // All these set the specified value to the backend, but neither emit changed signals + // nor change the member value. + void setVolumeInternal(int newVolume); + void setPlaybackRateInternal(qreal rate); + void setPositionInternal(qint64 position); + + void setMediaStatus(QMediaPlayer::MediaStatus status); + void setState(QMediaPlayer::State state); + + enum StopCommand { StopMmRenderer, IgnoreMmRenderer }; + void stopInternal(StopCommand stopCommand); + + QUrl m_media; + mmr_connection_t *m_connection; + int m_audioId; + QMediaPlayer::State m_state; + int m_volume; + bool m_muted; + qreal m_rate; + QPointer<MmRendererPlayerVideoRendererControl> m_videoRendererControl; + QPointer<MmRendererVideoWindowControl> m_videoWindowControl; + QPointer<MmRendererMetaDataReaderControl> m_metaDataReaderControl; + MmRendererMetaData m_metaData; + qint64 m_position; + QMediaPlayer::MediaStatus m_mediaStatus; + bool m_playAfterMediaLoaded; + bool m_inputAttached; + int m_bufferLevel; + QTimer m_loadingTimer; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice.cpp new file mode 100644 index 000000000..7ff9d9ea9 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrenderermediaplayerservice_p.h" + +#include "mmrenderermediaplayercontrol_p.h" +#include "mmrenderermetadatareadercontrol_p.h" +#include "mmrendererplayervideorenderercontrol_p.h" +#include "mmrendererutil_p.h" +#include "mmrenderervideowindowcontrol_p.h" + +#include "mmreventmediaplayercontrol_p.h" + +QT_BEGIN_NAMESPACE + +MmRendererMediaPlayerService::MmRendererMediaPlayerService(QObject *parent) + : QMediaService(parent), + m_videoRendererControl(0), + m_videoWindowControl(0), + m_mediaPlayerControl(0), + m_metaDataReaderControl(0), + m_appHasDrmPermission(false), + m_appHasDrmPermissionChecked(false) +{ +} + +MmRendererMediaPlayerService::~MmRendererMediaPlayerService() +{ + // Someone should have called releaseControl(), but better be safe + delete m_videoRendererControl; + delete m_videoWindowControl; + delete m_mediaPlayerControl; + delete m_metaDataReaderControl; +} + +QObject *MmRendererMediaPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) { + if (!m_mediaPlayerControl) { + m_mediaPlayerControl = new MmrEventMediaPlayerControl; + updateControls(); + } + return m_mediaPlayerControl; + } else if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) { + if (!m_metaDataReaderControl) { + m_metaDataReaderControl = new MmRendererMetaDataReaderControl(); + updateControls(); + } + return m_metaDataReaderControl; + } else if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (!m_appHasDrmPermissionChecked) { + m_appHasDrmPermission = checkForDrmPermission(); + m_appHasDrmPermissionChecked = true; + } + + if (m_appHasDrmPermission) { + // When the application wants to play back DRM secured media, we can't use + // the QVideoRendererControl, because we won't have access to the pixel data + // in this case. + return 0; + } + + if (!m_videoRendererControl) { + m_videoRendererControl = new MmRendererPlayerVideoRendererControl(); + updateControls(); + } + return m_videoRendererControl; + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + if (!m_videoWindowControl) { + m_videoWindowControl = new MmRendererVideoWindowControl(); + updateControls(); + } + return m_videoWindowControl; + } + return 0; +} + +void MmRendererMediaPlayerService::releaseControl(QObject *control) +{ + if (control == m_videoRendererControl) + m_videoRendererControl = 0; + if (control == m_videoWindowControl) + m_videoWindowControl = 0; + if (control == m_mediaPlayerControl) + m_mediaPlayerControl = 0; + if (control == m_metaDataReaderControl) + m_metaDataReaderControl = 0; + delete control; +} + +void MmRendererMediaPlayerService::updateControls() +{ + if (m_videoRendererControl && m_mediaPlayerControl) + m_mediaPlayerControl->setVideoRendererControl(m_videoRendererControl); + + if (m_videoWindowControl && m_mediaPlayerControl) + m_mediaPlayerControl->setVideoWindowControl(m_videoWindowControl); + + if (m_metaDataReaderControl && m_mediaPlayerControl) + m_mediaPlayerControl->setMetaDataReaderControl(m_metaDataReaderControl); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice_p.h new file mode 100644 index 000000000..07c7c000d --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermediaplayerservice_p.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERMEDIAPLAYERSERVICE_H +#define MMRENDERERMEDIAPLAYERSERVICE_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> +#include <QtCore/qpointer.h> + +QT_BEGIN_NAMESPACE + +class MmRendererMediaPlayerControl; +class MmRendererMetaDataReaderControl; +class MmRendererPlayerVideoRendererControl; +class MmRendererVideoWindowControl; + +class MmRendererMediaPlayerService : public QMediaService +{ + Q_OBJECT +public: + explicit MmRendererMediaPlayerService(QObject *parent = 0); + ~MmRendererMediaPlayerService(); + + QObject *requestControl(const char *name) override; + void releaseControl(QObject *control) override; + +private: + void updateControls(); + + QPointer<MmRendererPlayerVideoRendererControl> m_videoRendererControl; + QPointer<MmRendererVideoWindowControl> m_videoWindowControl; + QPointer<MmRendererMediaPlayerControl> m_mediaPlayerControl; + QPointer<MmRendererMetaDataReaderControl> m_metaDataReaderControl; + + bool m_appHasDrmPermission : 1; + bool m_appHasDrmPermissionChecked : 1; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata.cpp new file mode 100644 index 000000000..d369ea560 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata.cpp @@ -0,0 +1,298 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrenderermetadata_p.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qstringlist.h> + +#include <mm/renderer/events.h> +#include <sys/neutrino.h> +#include <sys/strm.h> + +static const char *strm_string_getx(const strm_string_t *sstr, const char *defaultValue) +{ + return sstr ? strm_string_get(sstr) : defaultValue; +} + +#if _NTO_VERSION < 700 +static strm_dict_t *mmr_metadata_split(strm_dict_t const *, const char *, unsigned) +{ + return nullptr; +} +#endif + +QT_BEGIN_NAMESPACE + +MmRendererMetaData::MmRendererMetaData() +{ + clear(); +} + +static const char * titleKey = "md_title_name"; +static const char * artistKey = "md_title_artist"; +static const char * commentKey = "md_title_comment"; +static const char * genreKey = "md_title_genre"; +static const char * yearKey = "md_title_year"; +static const char * durationKey = "md_title_duration"; +static const char * bitRateKey = "md_title_bitrate"; +static const char * sampleKey = "md_title_samplerate"; +static const char * albumKey = "md_title_album"; +static const char * trackKey = "md_title_track"; +static const char * widthKey = "md_video_width"; +static const char * heightKey = "md_video_height"; +static const char * mediaTypeKey = "md_title_mediatype"; +static const char * pixelWidthKey = "md_video_pixel_width"; +static const char * pixelHeightKey = "md_video_pixel_height"; +static const char * seekableKey = "md_title_seekable"; +static const char * trackSampleKey = "sample_rate"; +static const char * trackBitRateKey = "bitrate"; +static const char * trackWidthKey = "width"; +static const char * trackHeightKey = "height"; +static const char * trackPixelWidthKey = "pixel_width"; +static const char * trackPixelHeightKey = "pixel_height"; + +static const int mediaTypeAudioFlag = 4; +static const int mediaTypeVideoFlag = 2; + +bool MmRendererMetaData::update(const strm_dict_t *dict) +{ + if (!dict) { + clear(); + return true; + } + + const strm_string_t *value; + + value = strm_dict_find_rstr(dict, durationKey); + m_duration = QByteArray(strm_string_getx(value, "0")).toLongLong(); + + value = strm_dict_find_rstr(dict, mediaTypeKey); + m_mediaType = QByteArray(strm_string_getx(value, "-1")).toInt(); + + value = strm_dict_find_rstr(dict, titleKey); + m_title = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr))); + + value = strm_dict_find_rstr(dict, seekableKey); + m_seekable = (strcmp(strm_string_getx(value, "1"), "0") != 0); + + value = strm_dict_find_rstr(dict, artistKey); + m_artist = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr))); + + value = strm_dict_find_rstr(dict, commentKey); + m_comment = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr))); + + value = strm_dict_find_rstr(dict, genreKey); + m_genre = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr))); + + value = strm_dict_find_rstr(dict, yearKey); + m_year = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(dict, albumKey); + m_album = QString::fromLatin1(QByteArray(strm_string_getx(value, nullptr))); + + value = strm_dict_find_rstr(dict, trackKey); + m_track = QByteArray(strm_string_getx(value, "0")).toInt(); + + strm_dict_t *at = mmr_metadata_split(dict, "audio", 0); + if (at) { + value = strm_dict_find_rstr(at, trackSampleKey); + m_sampleRate = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(at, trackBitRateKey); + m_audioBitRate = QByteArray(strm_string_getx(value, "0")).toInt(); + + strm_dict_destroy(at); + } else { + value = strm_dict_find_rstr(dict, sampleKey); + m_sampleRate = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(dict, bitRateKey); + m_audioBitRate = QByteArray(strm_string_getx(value, "0")).toInt(); + } + + strm_dict_t *vt = mmr_metadata_split(dict, "video", 0); + if (vt) { + value = strm_dict_find_rstr(vt, trackWidthKey); + m_width = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(vt, trackHeightKey); + m_height = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(vt, trackPixelWidthKey); + m_pixelWidth = QByteArray(strm_string_getx(value, "1")).toFloat(); + + value = strm_dict_find_rstr(vt, trackPixelHeightKey); + m_pixelHeight = QByteArray(strm_string_getx(value, "1")).toFloat(); + + strm_dict_destroy(vt); + } else { + value = strm_dict_find_rstr(dict, widthKey); + m_width = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(dict, heightKey); + m_height = QByteArray(strm_string_getx(value, "0")).toInt(); + + value = strm_dict_find_rstr(dict, pixelWidthKey); + m_pixelWidth = QByteArray(strm_string_getx(value, "1")).toFloat(); + + value = strm_dict_find_rstr(dict, pixelHeightKey); + m_pixelHeight = QByteArray(strm_string_getx(value, "1")).toFloat(); + } + + return true; +} + +void MmRendererMetaData::clear() +{ + strm_dict_t *dict; + dict = strm_dict_new(); + update(dict); + strm_dict_destroy(dict); +} + +qlonglong MmRendererMetaData::duration() const +{ + return m_duration; +} + +// Handling of pixel aspect ratio +// +// If the pixel aspect ratio is different from 1:1, it means the video needs to be stretched in +// order to look natural. +// For example, if the pixel width is 2, and the pixel height is 1, it means a video of 300x200 +// pixels needs to be displayed as 600x200 to look correct. +// In order to support this the easiest way, we simply pretend that the actual size of the video +// is 600x200, which will cause the video to be displayed in an aspect ratio of 3:1 instead of 3:2, +// and therefore look correct. + +int MmRendererMetaData::height() const +{ + return m_height * m_pixelHeight; +} + +int MmRendererMetaData::width() const +{ + return m_width * m_pixelWidth; +} + +bool MmRendererMetaData::hasVideo() const +{ + // By default, assume no video if we can't extract the information + if (m_mediaType == -1) + return false; + + return (m_mediaType & mediaTypeVideoFlag); +} + +bool MmRendererMetaData::hasAudio() const +{ + // By default, assume audio only if we can't extract the information + if (m_mediaType == -1) + return true; + + return (m_mediaType & mediaTypeAudioFlag); +} + +QString MmRendererMetaData::title() const +{ + return m_title; +} + +bool MmRendererMetaData::isSeekable() const +{ + return m_seekable; +} + +QString MmRendererMetaData::artist() const +{ + return m_artist; +} + +QString MmRendererMetaData::comment() const +{ + return m_comment; +} + +QString MmRendererMetaData::genre() const +{ + return m_genre; +} + +int MmRendererMetaData::year() const +{ + return m_year; +} + +QString MmRendererMetaData::mediaType() const +{ + if (hasVideo()) + return QLatin1String("video"); + else if (hasAudio()) + return QLatin1String("audio"); + else + return QString(); +} + +int MmRendererMetaData::audioBitRate() const +{ + return m_audioBitRate; +} + +int MmRendererMetaData::sampleRate() const +{ + return m_sampleRate; +} + +QString MmRendererMetaData::album() const +{ + return m_album; +} + +int MmRendererMetaData::track() const +{ + return m_track; +} + +QSize MmRendererMetaData::resolution() const +{ + return QSize(width(), height()); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata_p.h new file mode 100644 index 000000000..72b10a110 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadata_p.h @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERMETADATA_H +#define MMRENDERERMETADATA_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/qglobal.h> +#include <QtCore/QSize> +#include <QtCore/QString> + +typedef struct strm_dict strm_dict_t; + +QT_BEGIN_NAMESPACE + +class MmRendererMetaData +{ +public: + MmRendererMetaData(); + bool update(const strm_dict_t *dict); + void clear(); + + // Duration in milliseconds + qlonglong duration() const; + + int height() const; + int width() const; + bool hasVideo() const; + bool hasAudio() const; + bool isSeekable() const; + + QString title() const; + QString artist() const; + QString comment() const; + QString genre() const; + int year() const; + QString mediaType() const; + int audioBitRate() const; + int sampleRate() const; + QString album() const; + int track() const; + QSize resolution() const; + +private: + qlonglong m_duration; + int m_height; + int m_width; + int m_mediaType; + float m_pixelWidth; + float m_pixelHeight; + bool m_seekable; + QString m_title; + QString m_artist; + QString m_comment; + QString m_genre; + int m_year; + int m_audioBitRate; + int m_sampleRate; + QString m_album; + int m_track; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol.cpp new file mode 100644 index 000000000..76ad98cb3 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol.cpp @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrenderermetadatareadercontrol_p.h" +#include <QtMultimedia/qmediametadata.h> + +QT_BEGIN_NAMESPACE + +MmRendererMetaDataReaderControl::MmRendererMetaDataReaderControl(QObject *parent) + : QMetaDataReaderControl(parent) +{ +} + +bool MmRendererMetaDataReaderControl::isMetaDataAvailable() const +{ + return !availableMetaData().isEmpty(); +} + +QVariant MmRendererMetaDataReaderControl::metaData(const QString &key) const +{ + if (key == QMediaMetaData::Title) + return m_metaData.title(); + else if (key == QMediaMetaData::AlbumArtist) + return m_metaData.artist(); + else if (key == QMediaMetaData::Comment) + return m_metaData.comment(); + else if (key == QMediaMetaData::Genre) + return m_metaData.genre(); + else if (key == QMediaMetaData::Year) + return m_metaData.year(); + else if (key == QMediaMetaData::MediaType) + return m_metaData.mediaType(); + else if (key == QMediaMetaData::Duration) + return m_metaData.duration(); + else if (key == QMediaMetaData::AudioBitRate) + return m_metaData.audioBitRate(); + else if (key == QMediaMetaData::SampleRate) + return m_metaData.sampleRate(); + else if (key == QMediaMetaData::AlbumTitle) + return m_metaData.album(); + else if (key == QMediaMetaData::TrackNumber) + return m_metaData.track(); + else if (key == QMediaMetaData::Resolution) + return m_metaData.resolution(); + + return QVariant(); +} + +QStringList MmRendererMetaDataReaderControl::availableMetaData() const +{ + QStringList metaData; + + if (!m_metaData.title().isEmpty()) + metaData << QMediaMetaData::Title; + if (!m_metaData.artist().isEmpty()) + metaData << QMediaMetaData::Author; + if (!m_metaData.comment().isEmpty()) + metaData << QMediaMetaData::Comment; + if (!m_metaData.genre().isEmpty()) + metaData << QMediaMetaData::Genre; + if (m_metaData.year() != 0) + metaData << QMediaMetaData::Year; + if (!m_metaData.mediaType().isEmpty()) + metaData << QMediaMetaData::MediaType; + if (m_metaData.duration() != 0) + metaData << QMediaMetaData::Duration; + if (m_metaData.audioBitRate() != 0) + metaData << QMediaMetaData::AudioBitRate; + if (m_metaData.sampleRate() != 0) + metaData << QMediaMetaData::SampleRate; + if (!m_metaData.album().isEmpty()) + metaData << QMediaMetaData::AlbumTitle; + if (m_metaData.track() != 0) + metaData << QMediaMetaData::TrackNumber; + if (m_metaData.resolution().isValid()) + metaData << QMediaMetaData::Resolution; + + return metaData; +} + +void MmRendererMetaDataReaderControl::setMetaData(const MmRendererMetaData &data) +{ + const MmRendererMetaData oldMetaData = m_metaData; + const bool oldMetaDataAvailable = isMetaDataAvailable(); + + m_metaData = data; + + bool changed = false; + if (m_metaData.title() != oldMetaData.title()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Title, m_metaData.title()); + } else if (m_metaData.artist() != oldMetaData.artist()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Author, m_metaData.artist()); + } else if (m_metaData.comment() != oldMetaData.comment()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Comment, m_metaData.comment()); + } else if (m_metaData.genre() != oldMetaData.genre()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Genre, m_metaData.genre()); + } else if (m_metaData.year() != oldMetaData.year()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Year, m_metaData.year()); + } else if (m_metaData.mediaType() != oldMetaData.mediaType()) { + changed = true; + emit metaDataChanged(QMediaMetaData::MediaType, m_metaData.mediaType()); + } else if (m_metaData.duration() != oldMetaData.duration()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Duration, m_metaData.duration()); + } else if (m_metaData.audioBitRate() != oldMetaData.audioBitRate()) { + changed = true; + emit metaDataChanged(QMediaMetaData::AudioBitRate, m_metaData.audioBitRate()); + } else if (m_metaData.sampleRate() != oldMetaData.sampleRate()) { + changed = true; + emit metaDataChanged(QMediaMetaData::SampleRate, m_metaData.sampleRate()); + } else if (m_metaData.album() != oldMetaData.album()) { + changed = true; + emit metaDataChanged(QMediaMetaData::AlbumTitle, m_metaData.album()); + } else if (m_metaData.track() != oldMetaData.track()) { + changed = true; + emit metaDataChanged(QMediaMetaData::TrackNumber, m_metaData.track()); + } else if (m_metaData.resolution() != oldMetaData.resolution()) { + changed = true; + emit metaDataChanged(QMediaMetaData::Resolution, m_metaData.resolution()); + } + + if (changed) + emit metaDataChanged(); + + const bool metaDataAvailable = isMetaDataAvailable(); + if (metaDataAvailable != oldMetaDataAvailable) + emit metaDataAvailableChanged(metaDataAvailable); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol_p.h new file mode 100644 index 000000000..f540f3f93 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderermetadatareadercontrol_p.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERMETADATAREADERCONTROL_H +#define MMRENDERERMETADATAREADERCONTROL_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 "mmrenderermetadata_p.h" +#include <qmetadatareadercontrol.h> + +QT_BEGIN_NAMESPACE + +class MmRendererMetaDataReaderControl : public QMetaDataReaderControl +{ + Q_OBJECT +public: + explicit MmRendererMetaDataReaderControl(QObject *parent = 0); + + bool isMetaDataAvailable() const override; + + QVariant metaData(const QString &key) const override; + QStringList availableMetaData() const override; + + void setMetaData(const MmRendererMetaData &data); + +private: + MmRendererMetaData m_metaData; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol.cpp new file mode 100644 index 000000000..d682c40a3 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol.cpp @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrendererplayervideorenderercontrol_p.h" + +#include "windowgrabber.h" + +#include <QCoreApplication> +#include <QDebug> +#include <QVideoSurfaceFormat> +#include <QOpenGLContext> + +#include <mm/renderer.h> + +QT_BEGIN_NAMESPACE + +static int winIdCounter = 0; + +MmRendererPlayerVideoRendererControl::MmRendererPlayerVideoRendererControl(QObject *parent) + : QVideoRendererControl(parent) + , m_windowGrabber(new WindowGrabber(this)) + , m_context(0) + , m_videoId(-1) +{ + connect(m_windowGrabber, SIGNAL(updateScene(const QSize &)), SLOT(updateScene(const QSize &))); +} + +MmRendererPlayerVideoRendererControl::~MmRendererPlayerVideoRendererControl() +{ + detachDisplay(); +} + +QAbstractVideoSurface *MmRendererPlayerVideoRendererControl::surface() const +{ + return m_surface; +} + +void MmRendererPlayerVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + m_surface = QPointer<QAbstractVideoSurface>(surface); + if (QOpenGLContext::currentContext()) + m_windowGrabber->checkForEglImageExtension(); + else if (m_surface) + m_surface->setProperty("_q_GLThreadCallback", QVariant::fromValue<QObject*>(this)); +} + +void MmRendererPlayerVideoRendererControl::attachDisplay(mmr_context_t *context) +{ + if (m_videoId != -1) { + qWarning() << "MmRendererPlayerVideoRendererControl: Video output already attached!"; + return; + } + + if (!context) { + qWarning() << "MmRendererPlayerVideoRendererControl: No media player context!"; + return; + } + + const QByteArray windowGroupId = m_windowGrabber->windowGroupId(); + if (windowGroupId.isEmpty()) { + qWarning() << "MmRendererPlayerVideoRendererControl: Unable to find window group"; + return; + } + + const QString windowName = QStringLiteral("MmRendererPlayerVideoRendererControl_%1_%2") + .arg(winIdCounter++) + .arg(QCoreApplication::applicationPid()); + + m_windowGrabber->setWindowId(windowName.toLatin1()); + + // Start with an invisible window, because we just want to grab the frames from it. + const QString videoDeviceUrl = QStringLiteral("screen:?winid=%1&wingrp=%2&initflags=invisible&nodstviewport=1") + .arg(windowName) + .arg(QString::fromLatin1(windowGroupId)); + + m_videoId = mmr_output_attach(context, videoDeviceUrl.toLatin1(), "video"); + if (m_videoId == -1) { + qWarning() << "mmr_output_attach() for video failed"; + return; + } + + m_context = context; +} + +void MmRendererPlayerVideoRendererControl::detachDisplay() +{ + m_windowGrabber->stop(); + + if (m_surface) + m_surface->stop(); + + if (m_context && m_videoId != -1) + mmr_output_detach(m_context, m_videoId); + + m_context = 0; + m_videoId = -1; +} + +void MmRendererPlayerVideoRendererControl::pause() +{ + m_windowGrabber->pause(); +} + +void MmRendererPlayerVideoRendererControl::resume() +{ + m_windowGrabber->resume(); +} + +class QnxTextureBuffer : public QAbstractVideoBuffer +{ +public: + QnxTextureBuffer(WindowGrabber *windowGrabber) : + QAbstractVideoBuffer(QAbstractVideoBuffer::GLTextureHandle) + { + m_windowGrabber = windowGrabber; + m_handle = 0; + } + MapMode mapMode() const { + return QAbstractVideoBuffer::ReadWrite; + } + void unmap() {} + MapData map(MapMode mode) override { return {}; } + QVariant handle() const { + if (!m_handle) { + const_cast<QnxTextureBuffer*>(this)->m_handle = m_windowGrabber->getNextTextureId(); + } + return m_handle; + } +private: + WindowGrabber *m_windowGrabber; + int m_handle; +}; + +void MmRendererPlayerVideoRendererControl::updateScene(const QSize &size) +{ + if (m_surface) { + if (!m_surface->isActive()) { + if (m_windowGrabber->eglImageSupported()) { + m_surface->start(QVideoSurfaceFormat(size, QVideoFrame::Format_BGR32, + QAbstractVideoBuffer::GLTextureHandle)); + } else { + m_surface->start(QVideoSurfaceFormat(size, QVideoFrame::Format_ARGB32)); + } + } else { + if (m_surface->surfaceFormat().frameSize() != size) { + m_surface->stop(); + if (m_windowGrabber->eglImageSupported()) { + m_surface->start(QVideoSurfaceFormat(size, QVideoFrame::Format_BGR32, + QAbstractVideoBuffer::GLTextureHandle)); + } else { + m_surface->start(QVideoSurfaceFormat(size, QVideoFrame::Format_ARGB32)); + } + } + } + + // Depending on the support of EGL images on the current platform we either pass a texture + // handle or a copy of the image data + if (m_windowGrabber->eglImageSupported()) { + QnxTextureBuffer *textBuffer = new QnxTextureBuffer(m_windowGrabber); + QVideoFrame actualFrame(textBuffer, size, QVideoFrame::Format_BGR32); + m_surface->present(actualFrame); + } else { + m_surface->present(m_windowGrabber->getNextImage().copy()); + } + } +} + +void MmRendererPlayerVideoRendererControl::customEvent(QEvent *e) +{ + // This is running in the render thread (OpenGL enabled) + if (e->type() == QEvent::User) + m_windowGrabber->checkForEglImageExtension(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol_p.h new file mode 100644 index 000000000..98a5304f3 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrendererplayervideorenderercontrol_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERPLAYERVIDEORENDERERCONTROL_H +#define MMRENDERERPLAYERVIDEORENDERERCONTROL_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 <QPointer> +#include <qabstractvideosurface.h> +#include <qvideorenderercontrol.h> + +typedef struct mmr_context mmr_context_t; + +QT_BEGIN_NAMESPACE + +class WindowGrabber; + +class MmRendererPlayerVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + explicit MmRendererPlayerVideoRendererControl(QObject *parent = 0); + ~MmRendererPlayerVideoRendererControl(); + + QAbstractVideoSurface *surface() const override; + void setSurface(QAbstractVideoSurface *surface) override; + + // Called by media control + void attachDisplay(mmr_context_t *context); + void detachDisplay(); + void pause(); + void resume(); + + void customEvent(QEvent *) override; + +private Q_SLOTS: + void updateScene(const QSize &size); + +private: + QPointer<QAbstractVideoSurface> m_surface; + + WindowGrabber* m_windowGrabber; + mmr_context_t *m_context; + + int m_videoId; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrendererutil.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrendererutil.cpp new file mode 100644 index 000000000..af19f7368 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrendererutil.cpp @@ -0,0 +1,246 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrendererutil_p.h" + +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QJsonDocument> +#include <QJsonObject> +#include <QJsonValue> +#include <QMutex> +#include <QMutex> +#include <QString> +#include <QXmlStreamReader> + +#include <mm/renderer.h> + +QT_BEGIN_NAMESPACE + +struct MmError { + int errorCode; + const char *name; +}; + +#define MM_ERROR_ENTRY(error) { error, #error } +static const MmError mmErrors[] = { + MM_ERROR_ENTRY(MMR_ERROR_NONE), + MM_ERROR_ENTRY(MMR_ERROR_UNKNOWN ), + MM_ERROR_ENTRY(MMR_ERROR_INVALID_PARAMETER ), + MM_ERROR_ENTRY(MMR_ERROR_INVALID_STATE), + MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_VALUE), + MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_MEDIA_TYPE), + MM_ERROR_ENTRY(MMR_ERROR_MEDIA_PROTECTED), + MM_ERROR_ENTRY(MMR_ERROR_UNSUPPORTED_OPERATION), + MM_ERROR_ENTRY(MMR_ERROR_READ), + MM_ERROR_ENTRY(MMR_ERROR_WRITE), + MM_ERROR_ENTRY(MMR_ERROR_MEDIA_UNAVAILABLE), + MM_ERROR_ENTRY(MMR_ERROR_MEDIA_CORRUPTED), + MM_ERROR_ENTRY(MMR_ERROR_OUTPUT_UNAVAILABLE), + MM_ERROR_ENTRY(MMR_ERROR_NO_MEMORY), + MM_ERROR_ENTRY(MMR_ERROR_RESOURCE_UNAVAILABLE), + MM_ERROR_ENTRY(MMR_ERROR_MEDIA_DRM_NO_RIGHTS), + MM_ERROR_ENTRY(MMR_ERROR_DRM_CORRUPTED_DATA_STORE), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OUTPUT_PROTECTION), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_HDMI), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_DISPLAYPORT), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_DVI), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_ANALOG_VIDEO), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_ANALOG_AUDIO), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_TOSLINK), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_SPDIF), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_BLUETOOTH), + MM_ERROR_ENTRY(MMR_ERROR_DRM_OPL_WIRELESSHD), +}; +static const unsigned int numMmErrors = sizeof(mmErrors) / sizeof(MmError); + +static QBasicMutex roleMapMutex; +static bool roleMapInitialized = false; +static QString roleMap[QAudio::CustomRole + 1]; + +template <typename T, size_t N> +constexpr size_t countof(T (&)[N]) +{ + return N; +} + +constexpr bool inBounds(QAudio::Role r) +{ + return r >= 0 && r < countof(roleMap); +} + +QString keyValueMapsLocation() +{ + QByteArray qtKeyValueMaps = qgetenv("QT_KEY_VALUE_MAPS"); + if (qtKeyValueMaps.isNull()) + return QStringLiteral("/etc/qt/keyvaluemaps"); + else + return qtKeyValueMaps; +} + +QJsonObject loadMapObject(const QString &keyValueMapPath) +{ + QFile mapFile(keyValueMapsLocation() + keyValueMapPath); + if (mapFile.open(QIODevice::ReadOnly)) { + QByteArray mapFileContents = mapFile.readAll(); + QJsonDocument mapDocument = QJsonDocument::fromJson(mapFileContents); + if (mapDocument.isObject()) { + QJsonObject mapObject = mapDocument.object(); + return mapObject; + } + } + return QJsonObject(); +} + +static void loadRoleMap() +{ + QMutexLocker locker(&roleMapMutex); + + if (!roleMapInitialized) { + QJsonObject mapObject = loadMapObject("/QAudio/Role.json"); + if (!mapObject.isEmpty()) { + // Wrapping the loads in a switch like this ensures that anyone adding + // a new enumerator will be notified that this code must be updated. A + // compile error will occur because the enumerator is missing from the + // switch. A compile error will also occur if the enumerator used to + // size the mapping table isn't updated when a new enumerator is added. + // One or more enumerators will be outside the bounds of the array when + // the wrong enumerator is used to size the array. + // + // The code loads a mapping for each enumerator because role is set + // to UnknownRole and all the cases drop through to the next case. +#pragma GCC diagnostic push +#pragma GCC diagnostic error "-Wswitch" +#define loadRoleMapping(r) \ + case QAudio::r: \ + static_assert(inBounds(QAudio::r), #r " out-of-bounds." \ + " Do you need to change the enumerator used to size the mapping table" \ + " because you added new QAudio::Role enumerators?"); \ + roleMap[QAudio::r] = mapObject.value(QLatin1String(#r)).toString(); + + QAudio::Role role = QAudio::UnknownRole; + switch (role) { + loadRoleMapping(UnknownRole); + loadRoleMapping(MusicRole); + loadRoleMapping(VideoRole); + loadRoleMapping(VoiceCommunicationRole); + loadRoleMapping(AlarmRole); + loadRoleMapping(NotificationRole); + loadRoleMapping(RingtoneRole); + loadRoleMapping(AccessibilityRole); + loadRoleMapping(SonificationRole); + loadRoleMapping(GameRole); + loadRoleMapping(CustomRole); + } +#undef loadRoleMapping +#pragma GCC diagnostic pop + + if (!roleMap[QAudio::CustomRole].isEmpty()) { + qWarning("CustomRole mapping ignored"); + roleMap[QAudio::CustomRole].clear(); + } + } + + roleMapInitialized = true; + } +} + +QString mmErrorMessage(const QString &msg, mmr_context_t *context, int *errorCode) +{ + const mmr_error_info_t * const mmError = mmr_error_info(context); + + if (errorCode) + *errorCode = mmError->error_code; + + if (mmError->error_code < numMmErrors) { + return QString("%1: %2 (code %3)").arg(msg).arg(mmErrors[mmError->error_code].name) + .arg(mmError->error_code); + } else { + return QString("%1: Unknown error code %2").arg(msg).arg(mmError->error_code); + } +} + +bool checkForDrmPermission() +{ + QDir sandboxDir = QDir::home(); // always returns 'data' directory + sandboxDir.cdUp(); // change to app sandbox directory + + QFile file(sandboxDir.filePath("app/native/bar-descriptor.xml")); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << "checkForDrmPermission: Unable to open bar-descriptor.xml"; + return false; + } + + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + reader.readNextStartElement(); + if (reader.name() == QLatin1String("action") + || reader.name() == QLatin1String("permission")) { + if (reader.readElementText().trimmed() == QLatin1String("access_protected_media")) + return true; + } + } + + return false; +} + +QString qnxAudioType(QAudio::Role role) +{ + loadRoleMap(); + + if (role >= 0 && role < countof(roleMap)) + return roleMap[role]; + else + return QString(); +} + +QList<QAudio::Role> qnxSupportedAudioRoles() +{ + loadRoleMap(); + + QList<QAudio::Role> result; + for (size_t i = 0; i < countof(roleMap); ++i) { + if (!roleMap[i].isEmpty() || (i == QAudio::UnknownRole)) + result.append(static_cast<QAudio::Role>(i)); + } + + return result; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrendererutil_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrendererutil_p.h new file mode 100644 index 000000000..9bba99487 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrendererutil_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERUTIL_H +#define MMRENDERERUTIL_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/qglobal.h> +#include <QtMultimedia/qaudio.h> + +typedef struct mmr_context mmr_context_t; + +QT_BEGIN_NAMESPACE + +class QString; + +QString mmErrorMessage(const QString &msg, mmr_context_t *context, int * errorCode = 0); + +bool checkForDrmPermission(); + +QString qnxAudioType(QAudio::Role role); +QList<QAudio::Role> qnxSupportedAudioRoles(); + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol.cpp b/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol.cpp new file mode 100644 index 000000000..0bc73b621 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol.cpp @@ -0,0 +1,413 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "mmrenderervideowindowcontrol_p.h" +#include "mmrendererutil_p.h" +#include <QtCore/qdebug.h> +#include <QtGui/qguiapplication.h> +#include <QtGui/qpa/qplatformnativeinterface.h> +#include <QtGui/qscreen.h> +#include <QtGui/qwindow.h> +#include <mm/renderer.h> + +QT_BEGIN_NAMESPACE + +static int winIdCounter = 0; + +MmRendererVideoWindowControl::MmRendererVideoWindowControl(QObject *parent) + : QVideoWindowControl(parent), + m_videoId(-1), + m_winId(0), + m_context(0), + m_fullscreen(false), + m_aspectRatioMode(Qt::IgnoreAspectRatio), + m_window(0), + m_hue(0), + m_brightness(0), + m_contrast(0), + m_saturation(0) +{ +} + +MmRendererVideoWindowControl::~MmRendererVideoWindowControl() +{ +} + +WId MmRendererVideoWindowControl::winId() const +{ + return m_winId; +} + +void MmRendererVideoWindowControl::setWinId(WId id) +{ + m_winId = id; +} + +QRect MmRendererVideoWindowControl::displayRect() const +{ + return m_displayRect ; +} + +void MmRendererVideoWindowControl::setDisplayRect(const QRect &rect) +{ + if (m_displayRect != rect) { + m_displayRect = rect; + updateVideoPosition(); + } +} + +bool MmRendererVideoWindowControl::isFullScreen() const +{ + return m_fullscreen; +} + +void MmRendererVideoWindowControl::setFullScreen(bool fullScreen) +{ + if (m_fullscreen != fullScreen) { + m_fullscreen = fullScreen; + updateVideoPosition(); + emit fullScreenChanged(m_fullscreen); + } +} + +void MmRendererVideoWindowControl::repaint() +{ + // Nothing we can or should do here +} + +QSize MmRendererVideoWindowControl::nativeSize() const +{ + return QSize(m_metaData.width(), m_metaData.height()); +} + +Qt::AspectRatioMode MmRendererVideoWindowControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void MmRendererVideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + m_aspectRatioMode = mode; +} + +int MmRendererVideoWindowControl::brightness() const +{ + return m_brightness; +} + +void MmRendererVideoWindowControl::setBrightness(int brightness) +{ + if (m_brightness != brightness) { + m_brightness = brightness; + updateBrightness(); + emit brightnessChanged(m_brightness); + } +} + +int MmRendererVideoWindowControl::contrast() const +{ + return m_contrast; +} + +void MmRendererVideoWindowControl::setContrast(int contrast) +{ + if (m_contrast != contrast) { + m_contrast = contrast; + updateContrast(); + emit contrastChanged(m_contrast); + } +} + +int MmRendererVideoWindowControl::hue() const +{ + return m_hue; +} + +void MmRendererVideoWindowControl::setHue(int hue) +{ + if (m_hue != hue) { + m_hue = hue; + updateHue(); + emit hueChanged(m_hue); + } +} + +int MmRendererVideoWindowControl::saturation() const +{ + return m_saturation; +} + +void MmRendererVideoWindowControl::setSaturation(int saturation) +{ + if (m_saturation != saturation) { + m_saturation = saturation; + updateSaturation(); + emit saturationChanged(m_saturation); + } +} + +void MmRendererVideoWindowControl::attachDisplay(mmr_context_t *context) +{ + if (m_videoId != -1) { + qDebug() << "MmRendererVideoWindowControl: Video output already attached!"; + return; + } + + if (!context) { + qDebug() << "MmRendererVideoWindowControl: No media player context!"; + return; + } + + QWindow *window = findWindow(m_winId); + if (!window) { + qDebug() << "MmRendererVideoWindowControl: No video window!"; + return; + } + + QPlatformNativeInterface * const nativeInterface = QGuiApplication::platformNativeInterface(); + if (!nativeInterface) { + qDebug() << "MmRendererVideoWindowControl: Unable to get platform native interface"; + return; + } + + const char * const groupNameData = static_cast<const char *>( + nativeInterface->nativeResourceForWindow("windowGroup", window)); + if (!groupNameData) { + qDebug() << "MmRendererVideoWindowControl: Unable to find window group for window" + << window; + return; + } + + const QString groupName = QString::fromLatin1(groupNameData); + m_windowName = QString("MmRendererVideoWindowControl_%1_%2").arg(winIdCounter++) + .arg(QCoreApplication::applicationPid()); + + nativeInterface->setWindowProperty(window->handle(), + QStringLiteral("mmRendererWindowName"), m_windowName); + + // Start with an invisible window. If it would be visible right away, it would be at the wrong + // position, and we can only change the position once we get the window handle. + const QString videoDeviceUrl = + QString("screen:?winid=%1&wingrp=%2&initflags=invisible&nodstviewport=1").arg(m_windowName).arg(groupName); + + m_videoId = mmr_output_attach(context, videoDeviceUrl.toLatin1(), "video"); + if (m_videoId == -1) { + qDebug() << mmErrorMessage("mmr_output_attach() for video failed", context); + return; + } + + m_context = context; + updateVideoPosition(); + updateHue(); + updateContrast(); + updateBrightness(); + updateSaturation(); +} + +void MmRendererVideoWindowControl::updateVideoPosition() +{ + QWindow * const window = findWindow(m_winId); + if (m_context && m_videoId != -1 && window) { + QPoint topLeft = m_displayRect.topLeft(); + + QScreen * const screen = window->screen(); + int width = m_fullscreen ? + screen->size().width() : + m_displayRect.width(); + int height = m_fullscreen ? + screen->size().height() : + m_displayRect.height(); + + if (m_metaData.hasVideo() && m_metaData.width() > 0 && m_metaData.height() > 0) { + // We need the source size to do aspect ratio scaling + const qreal sourceRatio = m_metaData.width() / static_cast<float>(m_metaData.height()); + const qreal targetRatio = width / static_cast<float>(height); + + if (m_aspectRatioMode == Qt::KeepAspectRatio) { + if (targetRatio < sourceRatio) { + // Need to make height smaller + const int newHeight = width / sourceRatio; + const int heightDiff = height - newHeight; + topLeft.ry() += heightDiff / 2; + height = newHeight; + } else { + // Need to make width smaller + const int newWidth = sourceRatio * height; + const int widthDiff = width - newWidth; + topLeft.rx() += widthDiff / 2; + width = newWidth; + } + + } else if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) { + if (targetRatio < sourceRatio) { + // Need to make width larger + const int newWidth = sourceRatio * height; + const int widthDiff = newWidth - width; + topLeft.rx() -= widthDiff / 2; + width = newWidth; + } else { + // Need to make height larger + const int newHeight = width / sourceRatio; + const int heightDiff = newHeight - height; + topLeft.ry() -= heightDiff / 2; + height = newHeight; + } + } + } + + if (m_window != 0) { + const int position[2] = { topLeft.x(), topLeft.y() }; + const int size[2] = { width, height }; + const int visible = m_displayRect.isValid(); + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_POSITION, position) != 0) + perror("Setting video position failed"); + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_SIZE, size) != 0) + perror("Setting video size failed"); + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_VISIBLE, &visible) != 0) + perror("Setting video visibility failed"); + } + } +} + +void MmRendererVideoWindowControl::updateBrightness() +{ + if (m_window != 0) { + const int backendValue = m_brightness * 2.55f; + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_BRIGHTNESS, &backendValue) != 0) + perror("Setting brightness failed"); + } +} + +void MmRendererVideoWindowControl::updateContrast() +{ + if (m_window != 0) { + const int backendValue = m_contrast * 1.27f; + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_CONTRAST, &backendValue) != 0) + perror("Setting contrast failed"); + } +} + +void MmRendererVideoWindowControl::updateHue() +{ + if (m_window != 0) { + const int backendValue = m_hue * 1.27f; + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_HUE, &backendValue) != 0) + perror("Setting hue failed"); + } +} + +void MmRendererVideoWindowControl::updateSaturation() +{ + if (m_window != 0) { + const int backendValue = m_saturation * 1.27f; + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_SATURATION, &backendValue) != 0) + perror("Setting saturation failed"); + } +} + +void MmRendererVideoWindowControl::detachDisplay() +{ + if (m_context && m_videoId != -1) + mmr_output_detach(m_context, m_videoId); + + m_context = 0; + m_videoId = -1; + m_metaData.clear(); + m_windowName.clear(); + m_window = 0; + m_hue = 0; + m_brightness = 0; + m_contrast = 0; + m_saturation = 0; +} + +void MmRendererVideoWindowControl::setMetaData(const MmRendererMetaData &metaData) +{ + m_metaData = metaData; + emit nativeSizeChanged(); + + // To handle the updated source size data + updateVideoPosition(); +} + +void MmRendererVideoWindowControl::screenEventHandler(const screen_event_t &screen_event) +{ + int eventType; + if (screen_get_event_property_iv(screen_event, SCREEN_PROPERTY_TYPE, &eventType) != 0) { + perror("MmRendererVideoWindowControl: Failed to query screen event type"); + return; + } + + if (eventType != SCREEN_EVENT_CREATE) + return; + + screen_window_t window = 0; + if (screen_get_event_property_pv(screen_event, SCREEN_PROPERTY_WINDOW, (void**)&window) != 0) { + perror("MmRendererVideoWindowControl: Failed to query window property"); + return; + } + + const int maxIdStrLength = 128; + char idString[maxIdStrLength]; + if (screen_get_window_property_cv(window, SCREEN_PROPERTY_ID_STRING, maxIdStrLength, idString) != 0) { + perror("MmRendererVideoWindowControl: Failed to query window ID string"); + return; + } + + if (m_windowName == idString) { + m_window = window; + updateVideoPosition(); + + const int visibleFlag = 1; + if (screen_set_window_property_iv(m_window, SCREEN_PROPERTY_VISIBLE, &visibleFlag) != 0) { + perror("MmRendererVideoWindowControl: Failed to make window visible"); + return; + } + } +} + +QWindow *MmRendererVideoWindowControl::findWindow(WId id) const +{ + const auto allWindows = QGuiApplication::allWindows(); + for (QWindow *window : allWindows) + if (window->winId() == id) + return window; + return 0; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol_p.h b/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol_p.h new file mode 100644 index 000000000..d318d99a1 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmrenderervideowindowcontrol_p.h @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 MMRENDERERVIDEOWINDOWCONTROL_H +#define MMRENDERERVIDEOWINDOWCONTROL_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 "mmrenderermetadata_p.h" +#include <qvideowindowcontrol.h> +#include <screen/screen.h> + +typedef struct mmr_context mmr_context_t; + +QT_BEGIN_NAMESPACE + +class MmRendererVideoWindowControl : public QVideoWindowControl +{ + Q_OBJECT +public: + explicit MmRendererVideoWindowControl(QObject *parent = 0); + ~MmRendererVideoWindowControl(); + + WId winId() const override; + void setWinId(WId id) override; + + QRect displayRect() const override; + void setDisplayRect(const QRect &rect) override; + + bool isFullScreen() const override; + void setFullScreen(bool fullScreen) override; + + void repaint() override; + + QSize nativeSize() const override; + + Qt::AspectRatioMode aspectRatioMode() const override; + void setAspectRatioMode(Qt::AspectRatioMode mode) override; + + int brightness() const override; + void setBrightness(int brightness) override; + + int contrast() const override; + void setContrast(int contrast) override; + + int hue() const override; + void setHue(int hue) override; + + int saturation() const override; + void setSaturation(int saturation) override; + + // + // Called by media control + // + void detachDisplay(); + void attachDisplay(mmr_context_t *context); + void setMetaData(const MmRendererMetaData &metaData); + void screenEventHandler(const screen_event_t &event); + +private: + QWindow *findWindow(WId id) const; + void updateVideoPosition(); + void updateBrightness(); + void updateContrast(); + void updateHue(); + void updateSaturation(); + + int m_videoId; + WId m_winId; + QRect m_displayRect; + mmr_context_t *m_context; + bool m_fullscreen; + MmRendererMetaData m_metaData; + Qt::AspectRatioMode m_aspectRatioMode; + QString m_windowName; + screen_window_t m_window; + int m_hue; + int m_brightness; + int m_contrast; + int m_saturation; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol.cpp b/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol.cpp new file mode 100644 index 000000000..f2e2b999b --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol.cpp @@ -0,0 +1,229 @@ +/**************************************************************************** +** +** Copyright (C) 2017 QNX Software Systems. All rights reserved. +** 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 "mmreventmediaplayercontrol_p.h" +#include "mmreventthread_p.h" +#include "mmrenderervideowindowcontrol_p.h" + +#include <mm/renderer.h> +#include <tuple> + +QT_BEGIN_NAMESPACE + +static std::tuple<int, int, bool> parseBufferLevel(const QByteArray &value) +{ + const int slashPos = value.indexOf('/'); + if (slashPos <= 0) + return std::make_tuple(0, 0, false); + + bool ok = false; + const int level = value.left(slashPos).toInt(&ok); + if (!ok || level < 0) + return std::make_tuple(0, 0, false); + + const int capacity = value.mid(slashPos + 1).toInt(&ok); + if (!ok || capacity < 0) + return std::make_tuple(0, 0, false); + + return std::make_tuple(level, capacity, true); +} + +MmrEventMediaPlayerControl::MmrEventMediaPlayerControl(QObject *parent) + : MmRendererMediaPlayerControl(parent) + , m_eventThread(nullptr) + , m_bufferStatus("") + , m_bufferLevel(0) + , m_bufferCapacity(0) + , m_position(0) + , m_suspended(false) + , m_suspendedReason("unknown") + , m_state(MMR_STATE_IDLE) + , m_speed(0) +{ + openConnection(); +} + +MmrEventMediaPlayerControl::~MmrEventMediaPlayerControl() +{ + destroy(); +} + +void MmrEventMediaPlayerControl::startMonitoring() +{ + m_eventThread = new MmrEventThread(m_context); + + connect(m_eventThread, &MmrEventThread::eventPending, + this, &MmrEventMediaPlayerControl::readEvents); + + m_eventThread->setObjectName(QStringLiteral("MmrEventThread-") + QString::number(m_id)); + m_eventThread->start(); +} + +void MmrEventMediaPlayerControl::stopMonitoring() +{ + delete m_eventThread; + m_eventThread = nullptr; +} + +void MmrEventMediaPlayerControl::resetMonitoring() +{ + m_bufferStatus = ""; + m_bufferLevel = 0; + m_bufferCapacity = 0; + m_position = 0; + m_suspended = false; + m_suspendedReason = "unknown"; + m_state = MMR_STATE_IDLE; + m_speed = 0; +} + +bool MmrEventMediaPlayerControl::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) +{ + Q_UNUSED(result); + if (eventType == "screen_event_t") { + screen_event_t event = static_cast<screen_event_t>(message); + if (MmRendererVideoWindowControl *control = videoWindowControl()) + control->screenEventHandler(event); + } + + return false; +} + +void MmrEventMediaPlayerControl::readEvents() +{ + const mmr_event_t *event; + + while ((event = mmr_event_get(m_context))) { + if (event->type == MMR_EVENT_NONE) + break; + + switch (event->type) { + case MMR_EVENT_STATUS: { + if (event->data) { + const strm_string_t *value; + value = strm_dict_find_rstr(event->data, "bufferstatus"); + if (value) { + m_bufferStatus = QByteArray(strm_string_get(value)); + if (!m_suspended) + setMmBufferStatus(m_bufferStatus); + } + + value = strm_dict_find_rstr(event->data, "bufferlevel"); + if (value) { + const char *cstrValue = strm_string_get(value); + int level; + int capacity; + bool ok; + std::tie(level, capacity, ok) = parseBufferLevel(QByteArray(cstrValue)); + if (!ok) { + qCritical("Could not parse buffer capacity from '%s'", cstrValue); + } else { + m_bufferLevel = level; + m_bufferCapacity = capacity; + setMmBufferLevel(level, capacity); + } + } + + value = strm_dict_find_rstr(event->data, "suspended"); + if (value) { + if (!m_suspended) { + m_suspended = true; + m_suspendedReason = strm_string_get(value); + handleMmSuspend(m_suspendedReason); + } + } else if (m_suspended) { + m_suspended = false; + handleMmSuspendRemoval(m_bufferStatus); + } + } + + if (event->pos_str) { + const QByteArray valueBa = QByteArray(event->pos_str); + bool ok; + m_position = valueBa.toLongLong(&ok); + if (!ok) { + qCritical("Could not parse position from '%s'", valueBa.constData()); + } else { + setMmPosition(m_position); + } + } + break; + } + case MMR_EVENT_STATE: { + if (event->state == MMR_STATE_PLAYING && m_speed != event->speed) { + m_speed = event->speed; + if (m_speed == 0) + handleMmPause(); + else + handleMmPlay(); + } + break; + } + case MMR_EVENT_METADATA: { + updateMetaData(event->data); + break; + } + case MMR_EVENT_ERROR: + case MMR_EVENT_NONE: + case MMR_EVENT_OVERFLOW: + case MMR_EVENT_WARNING: + case MMR_EVENT_PLAYLIST: + case MMR_EVENT_INPUT: + case MMR_EVENT_OUTPUT: + case MMR_EVENT_CTXTPAR: + case MMR_EVENT_TRKPAR: + case MMR_EVENT_OTHER: { + break; + } + } + + // Currently, any exit from the playing state is considered a stop (end-of-media). + // If you ever need to separate end-of-media from things like "stopped unexpectedly" + // or "stopped because of an error", you'll find that end-of-media is signaled by an + // MMR_EVENT_ERROR of MMR_ERROR_NONE with state changed to MMR_STATE_STOPPED. + if (event->state != m_state && m_state == MMR_STATE_PLAYING) + handleMmStopped(); + m_state = event->state; + } + + if (m_eventThread) + m_eventThread->signalRead(); +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol_p.h b/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol_p.h new file mode 100644 index 000000000..f2cb41561 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmreventmediaplayercontrol_p.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2017 QNX Software Systems. All rights reserved. +** 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 MMREVENTMEDIAPLAYERCONTROL_H +#define MMREVENTMEDIAPLAYERCONTROL_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 "mmrenderermediaplayercontrol_p.h" + +#include <mm/renderer/events.h> + +QT_BEGIN_NAMESPACE + +class MmrEventThread; + +class MmrEventMediaPlayerControl final : public MmRendererMediaPlayerControl +{ + Q_OBJECT +public: + explicit MmrEventMediaPlayerControl(QObject *parent = 0); + ~MmrEventMediaPlayerControl() override; + + void startMonitoring() override; + void stopMonitoring() override; + void resetMonitoring() override; + + bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override; + +private Q_SLOTS: + void readEvents(); + +private: + MmrEventThread *m_eventThread; + + // status properties. + QByteArray m_bufferStatus; + int m_bufferLevel; + int m_bufferCapacity; + qint64 m_position; + bool m_suspended; + QByteArray m_suspendedReason; + + // state properties. + mmr_state_t m_state; + int m_speed; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/mediaplayer/mmreventthread.cpp b/src/multimedia/platform/qnx/mediaplayer/mmreventthread.cpp new file mode 100644 index 000000000..25f26e216 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmreventthread.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2017 QNX Software Systems. All rights reserved. +** 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 "mmreventthread.h" + +#include <QtCore/QDebug> + +#include <errno.h> +#include <mm/renderer/events.h> +#include <sys/neutrino.h> + +static const int c_mmrCode = _PULSE_CODE_MINAVAIL + 0; +static const int c_readCode = _PULSE_CODE_MINAVAIL + 1; +static const int c_quitCode = _PULSE_CODE_MINAVAIL + 2; + +MmrEventThread::MmrEventThread(mmr_context_t *context) + : QThread(), + m_mmrContext(context) +{ + if (Q_UNLIKELY((m_channelId = ChannelCreate(_NTO_CHF_DISCONNECT + | _NTO_CHF_UNBLOCK + | _NTO_CHF_PRIVATE)) == -1)) { + qFatal("MmrEventThread: Can't continue without a channel"); + } + + if (Q_UNLIKELY((m_connectionId = ConnectAttach(0, 0, m_channelId, + _NTO_SIDE_CHANNEL, 0)) == -1)) { + ChannelDestroy(m_channelId); + qFatal("MmrEventThread: Can't continue without a channel connection"); + } + + SIGEV_PULSE_INIT(&m_mmrEvent, m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_mmrCode, 0); +} + +MmrEventThread::~MmrEventThread() +{ + // block until thread terminates + shutdown(); + + ConnectDetach(m_connectionId); + ChannelDestroy(m_channelId); +} + +void MmrEventThread::run() +{ + int armResult = mmr_event_arm(m_mmrContext, &m_mmrEvent); + if (armResult > 0) + emit eventPending(); + + while (1) { + struct _pulse msg; + memset(&msg, 0, sizeof(msg)); + int receiveId = MsgReceive(m_channelId, &msg, sizeof(msg), nullptr); + if (receiveId == 0) { + if (msg.code == c_mmrCode) { + emit eventPending(); + } else if (msg.code == c_readCode) { + armResult = mmr_event_arm(m_mmrContext, &m_mmrEvent); + if (armResult > 0) + emit eventPending(); + } else if (msg.code == c_quitCode) { + break; + } else { + qWarning() << Q_FUNC_INFO << "Unexpected pulse" << msg.code; + } + } else if (receiveId > 0) { + qWarning() << Q_FUNC_INFO << "Unexpected message" << msg.code; + } else { + qWarning() << Q_FUNC_INFO << "MsgReceive error" << strerror(errno); + } + } +} + +void MmrEventThread::signalRead() +{ + MsgSendPulse(m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_readCode, 0); +} + +void MmrEventThread::shutdown() +{ + MsgSendPulse(m_connectionId, SIGEV_PULSE_PRIO_INHERIT, c_quitCode, 0); + + // block until thread terminates + wait(); +} diff --git a/src/multimedia/platform/qnx/mediaplayer/mmreventthread_p.h b/src/multimedia/platform/qnx/mediaplayer/mmreventthread_p.h new file mode 100644 index 000000000..946548686 --- /dev/null +++ b/src/multimedia/platform/qnx/mediaplayer/mmreventthread_p.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2017 QNX Software Systems. All rights reserved. +** 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 MMREVENTTHREAD_H +#define MMREVENTTHREAD_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/QThread> + +#include <sys/neutrino.h> +#include <sys/siginfo.h> + +QT_BEGIN_NAMESPACE + +typedef struct mmr_context mmr_context_t; + +class MmrEventThread : public QThread +{ + Q_OBJECT + +public: + MmrEventThread(mmr_context_t *context); + ~MmrEventThread() override; + + void signalRead(); + +protected: + void run() override; + +Q_SIGNALS: + void eventPending(); + +private: + void shutdown(); + + int m_channelId; + int m_connectionId; + struct sigevent m_mmrEvent; + mmr_context_t *m_mmrContext; +}; + +QT_END_NAMESPACE + +#endif // MMREVENTTHREAD_H diff --git a/src/multimedia/platform/qnx/neutrinoserviceplugin.cpp b/src/multimedia/platform/qnx/neutrinoserviceplugin.cpp new file mode 100644 index 000000000..25a26d28f --- /dev/null +++ b/src/multimedia/platform/qnx/neutrinoserviceplugin.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 "neutrinoserviceplugin.h" + +#include "mmrenderermediaplayerservice.h" + +QT_BEGIN_NAMESPACE + +NeutrinoServicePlugin::NeutrinoServicePlugin() +{ +} + +QMediaService *NeutrinoServicePlugin::create(const QString &key) +{ + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new MmRendererMediaPlayerService(); + + return 0; +} + +void NeutrinoServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/qnx/neutrinoserviceplugin_p.h b/src/multimedia/platform/qnx/neutrinoserviceplugin_p.h new file mode 100644 index 000000000..7fa4ac5fb --- /dev/null +++ b/src/multimedia/platform/qnx/neutrinoserviceplugin_p.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Research In Motion +** 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 NEUTRINOSERVICEPLUGIN_H +#define NEUTRINOSERVICEPLUGIN_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.h> + +QT_BEGIN_NAMESPACE + +class NeutrinoServicePlugin + : public QMediaServiceProviderPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "neutrino_mediaservice.json") +public: + NeutrinoServicePlugin(); + + QMediaService *create(const QString &key) override; + void release(QMediaService *service) override; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/qnx/qnx.pri b/src/multimedia/platform/qnx/qnx.pri index fa7146940..4fe13620c 100644 --- a/src/multimedia/platform/qnx/qnx.pri +++ b/src/multimedia/platform/qnx/qnx.pri @@ -1,13 +1,19 @@ LIBS += -lasound -HEADERS += platform/qnx/qnxaudiointerface_p.h \ - platform/qnx/qnxaudiodeviceinfo_p.h \ - platform/qnx/qnxaudioinput_p.h \ - platform/qnx/qnxaudiooutput_p.h \ - platform/qnx/qnxaudioutils_p.h +HEADERS += $$PWD/qnxaudiointerface_p.h \ + $$PWD/qnxaudiodeviceinfo_p.h \ + $$PWD/qnxaudioinput_p.h \ + $$PWD/qnxaudiooutput_p.h \ + $$PWD/qnxaudioutils_p.h \ + $$PWD/neutrinoserviceplugin_p.h -SOURCES += platform/qnx/qnxaudiointerface.cpp \ - platform/qnx/qnxaudiodeviceinfo.cpp \ - platform/qnx/qnxaudioinput.cpp \ - platform/qnx/qnxaudiooutput.cpp \ - platform/qnx/qnxaudioutils.cpp +SOURCES += $$PWD/qnxaudiointerface.cpp \ + $$PWD/qnxaudiodeviceinfo.cpp \ + $$PWD/qnxaudioinput.cpp \ + $$PWD/qnxaudiooutput.cpp \ + $$PWD/qnxaudioutils.cpp \ + $$PWD/neutrinoserviceplugin.cpp + +include(common/common.pri) +include(mediaplayer/mediaplayer.pri) +include(camera/camera.pri) diff --git a/src/multimedia/platform/qnx/qnxaudiodeviceinfo_p.h b/src/multimedia/platform/qnx/qnxaudiodeviceinfo_p.h index 6f09c9d10..b072af1a2 100644 --- a/src/multimedia/platform/qnx/qnxaudiodeviceinfo_p.h +++ b/src/multimedia/platform/qnx/qnxaudiodeviceinfo_p.h @@ -40,6 +40,17 @@ #ifndef QNXAUDIODEVICEINFO_H #define QNXAUDIODEVICEINFO_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 "qaudiosystem_p.h" QT_BEGIN_NAMESPACE diff --git a/src/multimedia/platform/qnx/qnxaudioinput_p.h b/src/multimedia/platform/qnx/qnxaudioinput_p.h index d0e3ca30b..59087e37b 100644 --- a/src/multimedia/platform/qnx/qnxaudioinput_p.h +++ b/src/multimedia/platform/qnx/qnxaudioinput_p.h @@ -40,6 +40,17 @@ #ifndef QNXAUDIOINPUT_H #define QNXAUDIOINPUT_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 "qaudiosystem_p.h" #include <QSocketNotifier> diff --git a/src/multimedia/platform/qnx/qnxaudiointerface_p.h b/src/multimedia/platform/qnx/qnxaudiointerface_p.h index 8fc50af5f..259bf94a9 100644 --- a/src/multimedia/platform/qnx/qnxaudiointerface_p.h +++ b/src/multimedia/platform/qnx/qnxaudiointerface_p.h @@ -40,6 +40,17 @@ #ifndef QNXAUDIOPLUGIN_H #define QNXAUDIOPLUGIN_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/qaudiosystem_p.h> QT_BEGIN_NAMESPACE diff --git a/src/multimedia/platform/qnx/qnxaudiooutput_p.h b/src/multimedia/platform/qnx/qnxaudiooutput_p.h index 4fff7ad38..55b5e7be5 100644 --- a/src/multimedia/platform/qnx/qnxaudiooutput_p.h +++ b/src/multimedia/platform/qnx/qnxaudiooutput_p.h @@ -40,6 +40,17 @@ #ifndef QNXAUDIOOUTPUT_H #define QNXAUDIOOUTPUT_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 "qaudiosystem_p.h" #include <QElapsedTimer> diff --git a/src/multimedia/platform/qnx/qnxaudioutils_p.h b/src/multimedia/platform/qnx/qnxaudioutils_p.h index b997e0c45..0696d986e 100644 --- a/src/multimedia/platform/qnx/qnxaudioutils_p.h +++ b/src/multimedia/platform/qnx/qnxaudioutils_p.h @@ -40,6 +40,17 @@ #ifndef QNXAUDIOUTILS_H #define QNXAUDIOUTILS_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 "qaudiosystem_p.h" #include <sys/asoundlib.h> diff --git a/src/multimedia/platform/wasapi/wasapi.pri b/src/multimedia/platform/wasapi/wasapi.pri index 5b7336b1b..eb60edcbb 100644 --- a/src/multimedia/platform/wasapi/wasapi.pri +++ b/src/multimedia/platform/wasapi/wasapi.pri @@ -2,16 +2,16 @@ LIBS += -lstrmiids -lole32 -loleaut32 -lwinmm HEADERS += \ - platform/wasapi/qwindowsaudiointerface_p.h \ - platform/wasapi/qwindowsaudiodeviceinfo_p.h \ - platform/wasapi/qwindowsaudioinput_p.h \ - platform/wasapi/qwindowsaudiooutput_p.h \ - platform/wasapi/qwindowsaudioutils_p.h + $$PWD/qwindowsaudiointerface_p.h \ + $$PWD/qwindowsaudiodeviceinfo_p.h \ + $$PWD/qwindowsaudioinput_p.h \ + $$PWD/qwindowsaudiooutput_p.h \ + $$PWD/qwindowsaudioutils_p.h SOURCES += \ - platform/wasapi/qwindowsaudiointerface.cpp \ - platform/wasapi/qwindowsaudiodeviceinfo.cpp \ - platform/wasapi/qwindowsaudioinput.cpp \ - platform/wasapi/qwindowsaudiooutput.cpp \ - platform/wasapi/qwindowsaudioutils.cpp + $$PWD/qwindowsaudiointerface.cpp \ + $$PWD/qwindowsaudiodeviceinfo.cpp \ + $$PWD/qwindowsaudioinput.cpp \ + $$PWD/qwindowsaudiooutput.cpp \ + $$PWD/qwindowsaudioutils.cpp diff --git a/src/multimedia/platform/wmf/decoder/decoder.pri b/src/multimedia/platform/wmf/decoder/decoder.pri new file mode 100644 index 000000000..93cc683d4 --- /dev/null +++ b/src/multimedia/platform/wmf/decoder/decoder.pri @@ -0,0 +1,12 @@ +INCLUDEPATH += $$PWD + +LIBS += -lmfreadwrite -lwmcodecdspuuid +QMAKE_USE += wmf + +HEADERS += \ + $$PWD/mfdecodersourcereader_p.h \ + $$PWD/mfaudiodecodercontrol_p.h + +SOURCES += \ + $$PWD/mfdecodersourcereader.cpp \ + $$PWD/mfaudiodecodercontrol.cpp diff --git a/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp new file mode 100644 index 000000000..f41e05ed9 --- /dev/null +++ b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol.cpp @@ -0,0 +1,486 @@ +/**************************************************************************** +** +** 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 "Wmcodecdsp.h" +#include "mfaudiodecodercontrol_p.h" + +MFAudioDecoderControl::MFAudioDecoderControl(QObject *parent) + : QAudioDecoderControl(parent) + , m_decoderSourceReader(new MFDecoderSourceReader) + , m_sourceResolver(new SourceResolver) + , m_resampler(0) + , m_state(QAudioDecoder::StoppedState) + , m_device(0) + , m_mfInputStreamID(0) + , m_mfOutputStreamID(0) + , m_bufferReady(false) + , m_duration(0) + , m_position(0) + , m_loadingSource(false) + , m_mfOutputType(0) + , m_convertSample(0) + , m_sourceReady(false) + , m_resamplerDirty(false) +{ + CoCreateInstance(CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (LPVOID*)(&m_resampler)); + if (!m_resampler) { + qCritical("MFAudioDecoderControl: Failed to create resampler(CLSID_CResamplerMediaObject)!"); + return; + } + m_resampler->AddInputStreams(1, &m_mfInputStreamID); + + connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady())); + connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleMediaSourceError(long))); + connect(m_decoderSourceReader, SIGNAL(finished()), this, SLOT(handleSourceFinished())); + + QAudioFormat defaultFormat; + defaultFormat.setCodec("audio/x-raw"); + setAudioFormat(defaultFormat); +} + +MFAudioDecoderControl::~MFAudioDecoderControl() +{ + if (m_mfOutputType) + m_mfOutputType->Release(); + m_decoderSourceReader->shutdown(); + m_decoderSourceReader->Release(); + m_sourceResolver->Release(); + if (m_resampler) + m_resampler->Release(); +} + +QAudioDecoder::State MFAudioDecoderControl::state() const +{ + return m_state; +} + +QString MFAudioDecoderControl::sourceFilename() const +{ + return m_sourceFilename; +} + +void MFAudioDecoderControl::onSourceCleared() +{ + bool positionDirty = false; + bool durationDirty = false; + if (m_position != 0) { + m_position = 0; + positionDirty = true; + } + if (m_duration != 0) { + m_duration = 0; + durationDirty = true; + } + if (positionDirty) + emit positionChanged(m_position); + if (durationDirty) + emit durationChanged(m_duration); +} + +void MFAudioDecoderControl::setSourceFilename(const QString &fileName) +{ + if (!m_device && m_sourceFilename == fileName) + return; + m_sourceReady = false; + m_sourceResolver->cancel(); + m_decoderSourceReader->setSource(0, m_audioFormat); + m_device = 0; + m_sourceFilename = fileName; + if (!m_sourceFilename.isEmpty()) { + m_sourceResolver->shutdown(); + QUrl url; + if (m_sourceFilename.startsWith(':')) + url = QUrl(QStringLiteral("qrc%1").arg(m_sourceFilename)); + else + url = QUrl::fromLocalFile(m_sourceFilename); + m_sourceResolver->load(url, 0); + m_loadingSource = true; + } else { + onSourceCleared(); + } + emit sourceChanged(); +} + +QIODevice* MFAudioDecoderControl::sourceDevice() const +{ + return m_device; +} + +void MFAudioDecoderControl::setSourceDevice(QIODevice *device) +{ + if (m_device == device && m_sourceFilename.isEmpty()) + return; + m_sourceReady = false; + m_sourceResolver->cancel(); + m_decoderSourceReader->setSource(0, m_audioFormat); + m_sourceFilename.clear(); + m_device = device; + if (m_device) { + m_sourceResolver->shutdown(); + m_sourceResolver->load(QUrl(), m_device); + m_loadingSource = true; + } else { + onSourceCleared(); + } + emit sourceChanged(); +} + +void MFAudioDecoderControl::updateResamplerOutputType() +{ + m_resamplerDirty = false; + if (m_audioFormat == m_sourceOutputFormat) + return; + HRESULT hr = m_resampler->SetOutputType(m_mfOutputStreamID, m_mfOutputType, 0); + if (SUCCEEDED(hr)) { + MFT_OUTPUT_STREAM_INFO streamInfo; + m_resampler->GetOutputStreamInfo(m_mfOutputStreamID, &streamInfo); + if ((streamInfo.dwFlags & (MFT_OUTPUT_STREAM_PROVIDES_SAMPLES | MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES)) == 0) { + //if resampler does not allocate output sample memory, we do it here + if (m_convertSample) { + m_convertSample->Release(); + m_convertSample = 0; + } + if (SUCCEEDED(MFCreateSample(&m_convertSample))) { + IMFMediaBuffer *mbuf = 0;; + if (SUCCEEDED(MFCreateMemoryBuffer(streamInfo.cbSize, &mbuf))) { + m_convertSample->AddBuffer(mbuf); + mbuf->Release(); + } + } + } + } else { + qWarning() << "MFAudioDecoderControl: failed to SetOutputType of resampler" << hr; + } +} + +void MFAudioDecoderControl::handleMediaSourceReady() +{ + m_loadingSource = false; + m_sourceReady = true; + IMFMediaType *mediaType = m_decoderSourceReader->setSource(m_sourceResolver->mediaSource(), m_audioFormat); + m_sourceOutputFormat = QAudioFormat(); + + if (mediaType) { + m_sourceOutputFormat = m_audioFormat; + QAudioFormat af = m_audioFormat; + + UINT32 val = 0; + if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &val))) { + m_sourceOutputFormat.setChannelCount(int(val)); + } + if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &val))) { + m_sourceOutputFormat.setSampleRate(int(val)); + } + if (SUCCEEDED(mediaType->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &val))) { + m_sourceOutputFormat.setSampleSize(int(val)); + } + + GUID subType; + if (SUCCEEDED(mediaType->GetGUID(MF_MT_SUBTYPE, &subType))) { + if (subType == MFAudioFormat_Float) { + m_sourceOutputFormat.setSampleType(QAudioFormat::Float); + } else if (m_sourceOutputFormat.sampleSize() == 8) { + m_sourceOutputFormat.setSampleType(QAudioFormat::UnSignedInt); + } else { + m_sourceOutputFormat.setSampleType(QAudioFormat::SignedInt); + } + } + if (m_sourceOutputFormat.sampleType() != QAudioFormat::Float) { + m_sourceOutputFormat.setByteOrder(QAudioFormat::LittleEndian); + } + + if (m_audioFormat.sampleType() != QAudioFormat::Float + && m_audioFormat.sampleType() != QAudioFormat::SignedInt) { + af.setSampleType(m_sourceOutputFormat.sampleType()); + } + if (af.sampleType() == QAudioFormat::SignedInt) { + af.setByteOrder(QAudioFormat::LittleEndian); + } + if (m_audioFormat.channelCount() <= 0) { + af.setChannelCount(m_sourceOutputFormat.channelCount()); + } + if (m_audioFormat.sampleRate() <= 0) { + af.setSampleRate(m_sourceOutputFormat.sampleRate()); + } + if (m_audioFormat.sampleSize() <= 0) { + af.setSampleSize(m_sourceOutputFormat.sampleSize()); + } + setAudioFormat(af); + } + + if (m_sourceResolver->mediaSource()) { + if (mediaType && m_resampler) { + HRESULT hr = S_OK; + hr = m_resampler->SetInputType(m_mfInputStreamID, mediaType, 0); + if (SUCCEEDED(hr)) { + updateResamplerOutputType(); + } else { + qWarning() << "MFAudioDecoderControl: failed to SetInputType of resampler" << hr; + } + } + IMFPresentationDescriptor *pd; + if (SUCCEEDED(m_sourceResolver->mediaSource()->CreatePresentationDescriptor(&pd))) { + UINT64 duration = 0; + pd->GetUINT64(MF_PD_DURATION, &duration); + pd->Release(); + duration /= 10000; + if (m_duration != qint64(duration)) { + m_duration = qint64(duration); + emit durationChanged(m_duration); + } + } + if (m_state == QAudioDecoder::DecodingState) { + activatePipeline(); + } + } else if (m_state != QAudioDecoder::StoppedState) { + m_state = QAudioDecoder::StoppedState; + emit stateChanged(m_state); + } +} + +void MFAudioDecoderControl::handleMediaSourceError(long hr) +{ + Q_UNUSED(hr); + m_loadingSource = false; + m_decoderSourceReader->setSource(0, m_audioFormat); + if (m_state != QAudioDecoder::StoppedState) { + m_state = QAudioDecoder::StoppedState; + emit stateChanged(m_state); + } +} + +void MFAudioDecoderControl::activatePipeline() +{ + Q_ASSERT(!m_bufferReady); + m_state = QAudioDecoder::DecodingState; + connect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded())); + if (m_resamplerDirty) { + updateResamplerOutputType(); + } + m_decoderSourceReader->reset(); + m_decoderSourceReader->readNextSample(); + if (m_position != 0) { + m_position = 0; + emit positionChanged(0); + } +} + +void MFAudioDecoderControl::start() +{ + if (m_state != QAudioDecoder::StoppedState) + return; + + if (m_loadingSource) { + //deferred starting + m_state = QAudioDecoder::DecodingState; + emit stateChanged(m_state); + return; + } + + if (!m_decoderSourceReader->mediaSource()) + return; + activatePipeline(); + emit stateChanged(m_state); +} + +void MFAudioDecoderControl::stop() +{ + if (m_state == QAudioDecoder::StoppedState) + return; + m_state = QAudioDecoder::StoppedState; + disconnect(m_decoderSourceReader, SIGNAL(sampleAdded()), this, SLOT(handleSampleAdded())); + if (m_bufferReady) { + m_bufferReady = false; + emit bufferAvailableChanged(m_bufferReady); + } + emit stateChanged(m_state); +} + +void MFAudioDecoderControl::handleSampleAdded() +{ + QList<IMFSample*> samples = m_decoderSourceReader->takeSamples(); + Q_ASSERT(samples.count() > 0); + Q_ASSERT(!m_bufferReady); + Q_ASSERT(m_resampler); + LONGLONG sampleStartTime = 0; + IMFSample *firstSample = samples.first(); + firstSample->GetSampleTime(&sampleStartTime); + QByteArray abuf; + if (m_sourceOutputFormat == m_audioFormat) { + //no need for resampling + for (IMFSample *s : qAsConst(samples)) { + IMFMediaBuffer *buffer; + s->ConvertToContiguousBuffer(&buffer); + DWORD bufLen = 0; + BYTE *buf = 0; + if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) { + abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen)); + buffer->Unlock(); + } + buffer->Release(); + LONGLONG sampleTime = 0, sampleDuration = 0; + s->GetSampleTime(&sampleTime); + s->GetSampleDuration(&sampleDuration); + m_position = qint64(sampleTime + sampleDuration) / 10000; + s->Release(); + } + } else { + for (IMFSample *s : qAsConst(samples)) { + HRESULT hr = m_resampler->ProcessInput(m_mfInputStreamID, s, 0); + if (SUCCEEDED(hr)) { + MFT_OUTPUT_DATA_BUFFER outputDataBuffer; + outputDataBuffer.dwStreamID = m_mfOutputStreamID; + while (true) { + outputDataBuffer.pEvents = 0; + outputDataBuffer.dwStatus = 0; + outputDataBuffer.pSample = m_convertSample; + DWORD status = 0; + if (SUCCEEDED(m_resampler->ProcessOutput(0, 1, &outputDataBuffer, &status))) { + IMFMediaBuffer *buffer; + outputDataBuffer.pSample->ConvertToContiguousBuffer(&buffer); + DWORD bufLen = 0; + BYTE *buf = 0; + if (SUCCEEDED(buffer->Lock(&buf, NULL, &bufLen))) { + abuf.push_back(QByteArray(reinterpret_cast<char*>(buf), bufLen)); + buffer->Unlock(); + } + buffer->Release(); + } else { + break; + } + } + } + LONGLONG sampleTime = 0, sampleDuration = 0; + s->GetSampleTime(&sampleTime); + s->GetSampleDuration(&sampleDuration); + m_position = qint64(sampleTime + sampleDuration) / 10000; + s->Release(); + } + } + // WMF uses 100-nanosecond units, QAudioDecoder uses milliseconds, QAudioBuffer uses microseconds... + m_cachedAudioBuffer = QAudioBuffer(abuf, m_audioFormat, qint64(sampleStartTime / 10)); + m_bufferReady = true; + emit positionChanged(m_position); + emit bufferAvailableChanged(m_bufferReady); + emit bufferReady(); +} + +void MFAudioDecoderControl::handleSourceFinished() +{ + stop(); + emit finished(); +} + +QAudioFormat MFAudioDecoderControl::audioFormat() const +{ + return m_audioFormat; +} + +void MFAudioDecoderControl::setAudioFormat(const QAudioFormat &format) +{ + if (m_audioFormat == format || !m_resampler) + return; + if (format.codec() != QLatin1String("audio/x-wav") && format.codec() != QLatin1String("audio/x-raw")) { + qWarning("MFAudioDecoderControl does not accept non-pcm audio format!"); + return; + } + m_audioFormat = format; + + if (m_audioFormat.isValid()) { + IMFMediaType *mediaType = 0; + MFCreateMediaType(&mediaType); + mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + if (format.sampleType() == QAudioFormat::Float) { + mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float); + } else { + mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + + mediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, UINT32(m_audioFormat.channelCount())); + mediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, UINT32(m_audioFormat.sampleRate())); + UINT32 alignmentBlock = UINT32(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8); + mediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, alignmentBlock); + UINT32 avgBytesPerSec = UINT32(m_audioFormat.sampleRate() * m_audioFormat.sampleSize() / 8 * m_audioFormat.channelCount()); + mediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, avgBytesPerSec); + mediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, UINT32(m_audioFormat.sampleSize())); + mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + + if (m_mfOutputType) + m_mfOutputType->Release(); + m_mfOutputType = mediaType; + } else { + if (m_mfOutputType) + m_mfOutputType->Release(); + m_mfOutputType = NULL; + } + + if (m_sourceReady && m_state == QAudioDecoder::StoppedState) { + updateResamplerOutputType(); + } else { + m_resamplerDirty = true; + } + + emit formatChanged(m_audioFormat); +} + +QAudioBuffer MFAudioDecoderControl::read() +{ + if (!m_bufferReady) + return QAudioBuffer(); + QAudioBuffer buffer = m_cachedAudioBuffer; + m_bufferReady = false; + emit bufferAvailableChanged(m_bufferReady); + m_decoderSourceReader->readNextSample(); + return buffer; +} + +bool MFAudioDecoderControl::bufferAvailable() const +{ + return m_bufferReady; +} + +qint64 MFAudioDecoderControl::position() const +{ + return m_position; +} + +qint64 MFAudioDecoderControl::duration() const +{ + return m_duration; +} diff --git a/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h new file mode 100644 index 000000000..98a3b9d02 --- /dev/null +++ b/src/multimedia/platform/wmf/decoder/mfaudiodecodercontrol_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** 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 MFAUDIODECODERCONTROL_H +#define MFAUDIODECODERCONTROL_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 "qaudiodecodercontrol.h" +#include "mfdecodersourcereader_p.h" +#include "private/sourceresolver_p.h" + +QT_USE_NAMESPACE + +class MFAudioDecoderControl : public QAudioDecoderControl +{ + Q_OBJECT +public: + MFAudioDecoderControl(QObject *parent = 0); + ~MFAudioDecoderControl(); + + QAudioDecoder::State state() const; + + QString sourceFilename() const; + void setSourceFilename(const QString &fileName); + + QIODevice* sourceDevice() const; + void setSourceDevice(QIODevice *device); + + void start(); + void stop(); + + QAudioFormat audioFormat() const; + void setAudioFormat(const QAudioFormat &format); + + QAudioBuffer read(); + bool bufferAvailable() const; + + qint64 position() const; + qint64 duration() const; + +private Q_SLOTS: + void handleMediaSourceReady(); + void handleMediaSourceError(long hr); + void handleSampleAdded(); + void handleSourceFinished(); + +private: + void updateResamplerOutputType(); + void activatePipeline(); + void onSourceCleared(); + + MFDecoderSourceReader *m_decoderSourceReader; + SourceResolver *m_sourceResolver; + IMFTransform *m_resampler; + QAudioDecoder::State m_state; + QString m_sourceFilename; + QIODevice *m_device; + QAudioFormat m_audioFormat; + DWORD m_mfInputStreamID; + DWORD m_mfOutputStreamID; + bool m_bufferReady; + QAudioBuffer m_cachedAudioBuffer; + qint64 m_duration; + qint64 m_position; + bool m_loadingSource; + IMFMediaType *m_mfOutputType; + IMFSample *m_convertSample; + QAudioFormat m_sourceOutputFormat; + bool m_sourceReady; + bool m_resamplerDirty; +}; + +#endif//MFAUDIODECODERCONTROL_H diff --git a/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp new file mode 100644 index 000000000..b2b9cf60d --- /dev/null +++ b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** 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 "mfdecodersourcereader_p.h" + +MFDecoderSourceReader::MFDecoderSourceReader(QObject *parent) + : m_cRef(1) + , m_sourceReader(0) + , m_source(0) +{ + Q_UNUSED(parent); +} + +void MFDecoderSourceReader::shutdown() +{ + if (m_source) { + m_source->Release(); + m_source = NULL; + } + if (m_sourceReader) { + m_sourceReader->Release(); + m_sourceReader = NULL; + } +} + +IMFMediaSource* MFDecoderSourceReader::mediaSource() +{ + return m_source; +} + +IMFMediaType* MFDecoderSourceReader::setSource(IMFMediaSource *source, const QAudioFormat &audioFormat) +{ + IMFMediaType *mediaType = NULL; + if (m_source == source) + return mediaType; + if (m_source) { + m_source->Release(); + m_source = NULL; + } + if (m_sourceReader) { + m_sourceReader->Release(); + m_sourceReader = NULL; + } + if (!source) + return mediaType; + IMFAttributes *attr = NULL; + MFCreateAttributes(&attr, 1); + if (SUCCEEDED(attr->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, this))) { + if (SUCCEEDED(MFCreateSourceReaderFromMediaSource(source, attr, &m_sourceReader))) { + m_source = source; + m_source->AddRef(); + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_ALL_STREAMS), FALSE); + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE); + IMFMediaType *pPartialType = NULL; + MFCreateMediaType(&pPartialType); + pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + + if (audioFormat.sampleType() == QAudioFormat::Float) { + pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_Float); + } else { + pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + + m_sourceReader->SetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), NULL, pPartialType); + pPartialType->Release(); + m_sourceReader->GetCurrentMediaType(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), &mediaType); + // Ensure the stream is selected. + m_sourceReader->SetStreamSelection(DWORD(MF_SOURCE_READER_FIRST_AUDIO_STREAM), TRUE); + } + attr->Release(); + } + return mediaType; +} + +void MFDecoderSourceReader::reset() +{ + if (!m_sourceReader) + return; + PROPVARIANT vPos; + PropVariantInit(&vPos); + vPos.vt = VT_I8; + vPos.uhVal.QuadPart = 0; + m_sourceReader->SetCurrentPosition(GUID_NULL, vPos); +} + +void MFDecoderSourceReader::readNextSample() +{ + if (!m_sourceReader) + return; + m_sourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, NULL, NULL, NULL); +} + +QList<IMFSample*> MFDecoderSourceReader::takeSamples() //internal samples will be cleared after this +{ + QList<IMFSample*> samples; + m_samplesMutex.lock(); + samples = m_cachedSamples; + m_cachedSamples.clear(); + m_samplesMutex.unlock(); + return samples; +} + +//from IUnknown +STDMETHODIMP MFDecoderSourceReader::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFSourceReaderCallback) { + *ppvObject = static_cast<IMFSourceReaderCallback*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFDecoderSourceReader::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFDecoderSourceReader::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + this->deleteLater(); + } + return cRef; +} + +//from IMFSourceReaderCallback +STDMETHODIMP MFDecoderSourceReader::OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample) +{ + Q_UNUSED(hrStatus); + Q_UNUSED(dwStreamIndex); + Q_UNUSED(llTimestamp); + if (pSample) { + pSample->AddRef(); + m_samplesMutex.lock(); + m_cachedSamples.push_back(pSample); + m_samplesMutex.unlock(); + emit sampleAdded(); + } else if ((dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) == MF_SOURCE_READERF_ENDOFSTREAM) { + emit finished(); + } + return S_OK; +} + +STDMETHODIMP MFDecoderSourceReader::OnFlush(DWORD) +{ + return S_OK; +} + +STDMETHODIMP MFDecoderSourceReader::OnEvent(DWORD, IMFMediaEvent*) +{ + return S_OK; +} diff --git a/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h new file mode 100644 index 000000000..7d63f5368 --- /dev/null +++ b/src/multimedia/platform/wmf/decoder/mfdecodersourcereader_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFDECODERSOURCEREADER_H +#define MFDECODERSOURCEREADER_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 <mfapi.h> +#include <mfidl.h> +#include <Mfreadwrite.h> + +#include <QtCore/qobject.h> +#include <QtCore/qmutex.h> +#include "qaudioformat.h" + +QT_USE_NAMESPACE + +class MFDecoderSourceReader : public QObject, public IMFSourceReaderCallback +{ + Q_OBJECT +public: + MFDecoderSourceReader(QObject *parent = 0); + void shutdown(); + + IMFMediaSource* mediaSource(); + IMFMediaType* setSource(IMFMediaSource *source, const QAudioFormat &audioFormat); + + void reset(); + void readNextSample(); + QList<IMFSample*> takeSamples(); //internal samples will be cleared after this + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + //from IMFSourceReaderCallback + STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex, + DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample); + STDMETHODIMP OnFlush(DWORD dwStreamIndex); + STDMETHODIMP OnEvent(DWORD dwStreamIndex, IMFMediaEvent *pEvent); + +Q_SIGNALS: + void sampleAdded(); + void finished(); + +private: + long m_cRef; + QList<IMFSample*> m_cachedSamples; + QMutex m_samplesMutex; + + IMFSourceReader *m_sourceReader; + IMFMediaSource *m_source; +}; +#endif//MFDECODERSOURCEREADER_H diff --git a/src/multimedia/platform/wmf/evr/evr.pri b/src/multimedia/platform/wmf/evr/evr.pri new file mode 100644 index 000000000..6168063e9 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evr.pri @@ -0,0 +1,20 @@ +INCLUDEPATH += $$PWD + +qtHaveModule(widgets): QT += widgets +QT += gui-private + +LIBS += -lmf -lmfplat -lmfuuid -ld3d9 -ldxva2 -lwinmm -levr + +HEADERS += \ + $$PWD/evrvideowindowcontrol_p.h \ + $$PWD/evrcustompresenter_p.h \ + $$PWD/evrd3dpresentengine_p.h \ + $$PWD/evrhelpers_p.h \ + $$PWD/evrdefs_p.h + +SOURCES += \ + $$PWD/evrvideowindowcontrol.cpp \ + $$PWD/evrcustompresenter.cpp \ + $$PWD/evrd3dpresentengine.cpp \ + $$PWD/evrhelpers.cpp \ + $$PWD/evrdefs.cpp diff --git a/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp b/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp new file mode 100644 index 000000000..dd6c0021b --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrcustompresenter.cpp @@ -0,0 +1,2062 @@ +/**************************************************************************** +** +** 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 "evrcustompresenter_p.h" + +#include "evrd3dpresentengine_p.h" +#include "evrhelpers_p.h" + +#include <QtCore/qmutex.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qrect.h> +#include <qabstractvideosurface.h> +#include <qthread.h> +#include <qcoreapplication.h> +#include <qmath.h> +#include <QtCore/qdebug.h> + +#include <mutex> + +#include <float.h> +#include <evcode.h> + +QT_BEGIN_NAMESPACE + +const static MFRatio g_DefaultFrameRate = { 30, 1 }; +static const DWORD SCHEDULER_TIMEOUT = 5000; +static const MFTIME ONE_SECOND = 10000000; +static const LONG ONE_MSEC = 1000; + +// Function declarations. +static HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG& hnsSampleTime, const LONGLONG& hnsDuration); +static HRESULT clearDesiredSampleTime(IMFSample *sample); +static HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect& nrcSource); +static QVideoFrame::PixelFormat pixelFormatFromMediaType(IMFMediaType *type); + +static inline LONG MFTimeToMsec(const LONGLONG& time) +{ + return (LONG)(time / (ONE_SECOND / ONE_MSEC)); +} + +bool qt_evr_setCustomPresenter(IUnknown *evr, EVRCustomPresenter *presenter) +{ + if (!evr || !presenter) + return false; + + HRESULT result = E_FAIL; + + IMFVideoRenderer *renderer = NULL; + if (SUCCEEDED(evr->QueryInterface(IID_PPV_ARGS(&renderer)))) { + result = renderer->InitializeRenderer(NULL, presenter); + renderer->Release(); + } + + return result == S_OK; +} + +class PresentSampleEvent : public QEvent +{ +public: + PresentSampleEvent(IMFSample *sample) + : QEvent(QEvent::Type(EVRCustomPresenter::PresentSample)) + , m_sample(sample) + { + if (m_sample) + m_sample->AddRef(); + } + + ~PresentSampleEvent() override + { + if (m_sample) + m_sample->Release(); + } + + IMFSample *sample() const { return m_sample; } + +private: + IMFSample *m_sample; +}; + +Scheduler::Scheduler(EVRCustomPresenter *presenter) + : m_presenter(presenter) + , m_clock(NULL) + , m_threadID(0) + , m_schedulerThread(0) + , m_threadReadyEvent(0) + , m_flushEvent(0) + , m_playbackRate(1.0f) + , m_perFrameInterval(0) + , m_perFrame_1_4th(0) + , m_lastSampleTime(0) +{ +} + +Scheduler::~Scheduler() +{ + qt_evr_safe_release(&m_clock); + for (int i = 0; i < m_scheduledSamples.size(); ++i) + m_scheduledSamples[i]->Release(); + m_scheduledSamples.clear(); +} + +void Scheduler::setFrameRate(const MFRatio& fps) +{ + UINT64 AvgTimePerFrame = 0; + + // Convert to a duration. + MFFrameRateToAverageTimePerFrame(fps.Numerator, fps.Denominator, &AvgTimePerFrame); + + m_perFrameInterval = (MFTIME)AvgTimePerFrame; + + // Calculate 1/4th of this value, because we use it frequently. + m_perFrame_1_4th = m_perFrameInterval / 4; +} + +HRESULT Scheduler::startScheduler(IMFClock *clock) +{ + if (m_schedulerThread) + return E_UNEXPECTED; + + HRESULT hr = S_OK; + DWORD dwID = 0; + HANDLE hObjects[2]; + DWORD dwWait = 0; + + if (m_clock) + m_clock->Release(); + m_clock = clock; + if (m_clock) + m_clock->AddRef(); + + // Set a high the timer resolution (ie, short timer period). + timeBeginPeriod(1); + + // Create an event to wait for the thread to start. + m_threadReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!m_threadReadyEvent) { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto done; + } + + // Create an event to wait for flush commands to complete. + m_flushEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!m_flushEvent) { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto done; + } + + // Create the scheduler thread. + m_schedulerThread = CreateThread(NULL, 0, schedulerThreadProc, (LPVOID)this, 0, &dwID); + if (!m_schedulerThread) { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto done; + } + + // Wait for the thread to signal the "thread ready" event. + hObjects[0] = m_threadReadyEvent; + hObjects[1] = m_schedulerThread; + dwWait = WaitForMultipleObjects(2, hObjects, FALSE, INFINITE); // Wait for EITHER of these handles. + if (WAIT_OBJECT_0 != dwWait) { + // The thread terminated early for some reason. This is an error condition. + CloseHandle(m_schedulerThread); + m_schedulerThread = NULL; + + hr = E_UNEXPECTED; + goto done; + } + + m_threadID = dwID; + +done: + // Regardless success/failure, we are done using the "thread ready" event. + if (m_threadReadyEvent) { + CloseHandle(m_threadReadyEvent); + m_threadReadyEvent = NULL; + } + return hr; +} + +HRESULT Scheduler::stopScheduler() +{ + if (!m_schedulerThread) + return S_OK; + + // Ask the scheduler thread to exit. + PostThreadMessage(m_threadID, Terminate, 0, 0); + + // Wait for the thread to exit. + WaitForSingleObject(m_schedulerThread, INFINITE); + + // Close handles. + CloseHandle(m_schedulerThread); + m_schedulerThread = NULL; + + CloseHandle(m_flushEvent); + m_flushEvent = NULL; + + // Discard samples. + m_mutex.lock(); + for (int i = 0; i < m_scheduledSamples.size(); ++i) + m_scheduledSamples[i]->Release(); + m_scheduledSamples.clear(); + m_mutex.unlock(); + + // Restore the timer resolution. + timeEndPeriod(1); + + return S_OK; +} + +HRESULT Scheduler::flush() +{ + if (m_schedulerThread) { + // Ask the scheduler thread to flush. + PostThreadMessage(m_threadID, Flush, 0 , 0); + + // Wait for the scheduler thread to signal the flush event, + // OR for the thread to terminate. + HANDLE objects[] = { m_flushEvent, m_schedulerThread }; + + WaitForMultipleObjects(ARRAYSIZE(objects), objects, FALSE, SCHEDULER_TIMEOUT); + } + + return S_OK; +} + +bool Scheduler::areSamplesScheduled() +{ + QMutexLocker locker(&m_mutex); + return m_scheduledSamples.count() > 0; +} + +HRESULT Scheduler::scheduleSample(IMFSample *sample, bool presentNow) +{ + if (!m_schedulerThread) + return MF_E_NOT_INITIALIZED; + + HRESULT hr = S_OK; + DWORD dwExitCode = 0; + + GetExitCodeThread(m_schedulerThread, &dwExitCode); + if (dwExitCode != STILL_ACTIVE) + return E_FAIL; + + if (presentNow || !m_clock) { + m_presenter->presentSample(sample); + } else { + // Queue the sample and ask the scheduler thread to wake up. + m_mutex.lock(); + sample->AddRef(); + m_scheduledSamples.enqueue(sample); + m_mutex.unlock(); + + if (SUCCEEDED(hr)) + PostThreadMessage(m_threadID, Schedule, 0, 0); + } + + return hr; +} + +HRESULT Scheduler::processSamplesInQueue(LONG *nextSleep) +{ + HRESULT hr = S_OK; + LONG wait = 0; + IMFSample *sample = NULL; + + // Process samples until the queue is empty or until the wait time > 0. + while (!m_scheduledSamples.isEmpty()) { + m_mutex.lock(); + sample = m_scheduledSamples.dequeue(); + m_mutex.unlock(); + + // Process the next sample in the queue. If the sample is not ready + // for presentation. the value returned in wait is > 0, which + // means the scheduler should sleep for that amount of time. + + hr = processSample(sample, &wait); + qt_evr_safe_release(&sample); + + if (FAILED(hr) || wait > 0) + break; + } + + // If the wait time is zero, it means we stopped because the queue is + // empty (or an error occurred). Set the wait time to infinite; this will + // make the scheduler thread sleep until it gets another thread message. + if (wait == 0) + wait = INFINITE; + + *nextSleep = wait; + return hr; +} + +HRESULT Scheduler::processSample(IMFSample *sample, LONG *pNextSleep) +{ + HRESULT hr = S_OK; + + LONGLONG hnsPresentationTime = 0; + LONGLONG hnsTimeNow = 0; + MFTIME hnsSystemTime = 0; + + bool presentNow = true; + LONG nextSleep = 0; + + if (m_clock) { + // Get the sample's time stamp. It is valid for a sample to + // have no time stamp. + hr = sample->GetSampleTime(&hnsPresentationTime); + + // Get the clock time. (But if the sample does not have a time stamp, + // we don't need the clock time.) + if (SUCCEEDED(hr)) + hr = m_clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime); + + // Calculate the time until the sample's presentation time. + // A negative value means the sample is late. + LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow; + if (m_playbackRate < 0) { + // For reverse playback, the clock runs backward. Therefore, the + // delta is reversed. + hnsDelta = - hnsDelta; + } + + if (hnsDelta < - m_perFrame_1_4th) { + // This sample is late. + presentNow = true; + } else if (hnsDelta > (3 * m_perFrame_1_4th)) { + // This sample is still too early. Go to sleep. + nextSleep = MFTimeToMsec(hnsDelta - (3 * m_perFrame_1_4th)); + + // Adjust the sleep time for the clock rate. (The presentation clock runs + // at m_fRate, but sleeping uses the system clock.) + if (m_playbackRate != 0) + nextSleep = (LONG)(nextSleep / qFabs(m_playbackRate)); + + // Don't present yet. + presentNow = false; + } + } + + if (presentNow) { + m_presenter->presentSample(sample); + } else { + // The sample is not ready yet. Return it to the queue. + m_mutex.lock(); + sample->AddRef(); + m_scheduledSamples.prepend(sample); + m_mutex.unlock(); + } + + *pNextSleep = nextSleep; + + return hr; +} + +DWORD WINAPI Scheduler::schedulerThreadProc(LPVOID parameter) +{ + Scheduler* scheduler = reinterpret_cast<Scheduler*>(parameter); + if (!scheduler) + return -1; + return scheduler->schedulerThreadProcPrivate(); +} + +DWORD Scheduler::schedulerThreadProcPrivate() +{ + HRESULT hr = S_OK; + MSG msg; + LONG wait = INFINITE; + bool exitThread = false; + + // Force the system to create a message queue for this thread. + // (See MSDN documentation for PostThreadMessage.) + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + + // Signal to the scheduler that the thread is ready. + SetEvent(m_threadReadyEvent); + + while (!exitThread) { + // Wait for a thread message OR until the wait time expires. + DWORD result = MsgWaitForMultipleObjects(0, NULL, FALSE, wait, QS_POSTMESSAGE); + + if (result == WAIT_TIMEOUT) { + // If we timed out, then process the samples in the queue + hr = processSamplesInQueue(&wait); + if (FAILED(hr)) + exitThread = true; + } + + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + bool processSamples = true; + + switch (msg.message) { + case Terminate: + exitThread = true; + break; + case Flush: + // Flushing: Clear the sample queue and set the event. + m_mutex.lock(); + for (int i = 0; i < m_scheduledSamples.size(); ++i) + m_scheduledSamples[i]->Release(); + m_scheduledSamples.clear(); + m_mutex.unlock(); + wait = INFINITE; + SetEvent(m_flushEvent); + break; + case Schedule: + // Process as many samples as we can. + if (processSamples) { + hr = processSamplesInQueue(&wait); + if (FAILED(hr)) + exitThread = true; + processSamples = (wait != (LONG)INFINITE); + } + break; + } + } + + } + + return (SUCCEEDED(hr) ? 0 : 1); +} + + +SamplePool::SamplePool() + : m_initialized(false) +{ +} + +SamplePool::~SamplePool() +{ + clear(); +} + +HRESULT SamplePool::getSample(IMFSample **sample) +{ + QMutexLocker locker(&m_mutex); + + if (!m_initialized) + return MF_E_NOT_INITIALIZED; + + if (m_videoSampleQueue.isEmpty()) + return MF_E_SAMPLEALLOCATOR_EMPTY; + + // Get a sample from the allocated queue. + + // It doesn't matter if we pull them from the head or tail of the list, + // but when we get it back, we want to re-insert it onto the opposite end. + // (see ReturnSample) + + IMFSample *taken = m_videoSampleQueue.takeFirst(); + + // Give the sample to the caller. + *sample = taken; + (*sample)->AddRef(); + + taken->Release(); + + return S_OK; +} + +HRESULT SamplePool::returnSample(IMFSample *sample) +{ + QMutexLocker locker(&m_mutex); + + if (!m_initialized) + return MF_E_NOT_INITIALIZED; + + m_videoSampleQueue.append(sample); + sample->AddRef(); + + return S_OK; +} + +HRESULT SamplePool::initialize(QList<IMFSample*> &samples) +{ + QMutexLocker locker(&m_mutex); + + if (m_initialized) + return MF_E_INVALIDREQUEST; + + // Move these samples into our allocated queue. + for (auto sample : qAsConst(samples)) { + sample->AddRef(); + m_videoSampleQueue.append(sample); + } + + m_initialized = true; + + for (auto sample : qAsConst(samples)) + sample->Release(); + samples.clear(); + return S_OK; +} + +HRESULT SamplePool::clear() +{ + QMutexLocker locker(&m_mutex); + + for (auto sample : qAsConst(m_videoSampleQueue)) + sample->Release(); + m_videoSampleQueue.clear(); + m_initialized = false; + + return S_OK; +} + + +EVRCustomPresenter::EVRCustomPresenter(QAbstractVideoSurface *surface) + : QObject() + , m_sampleFreeCB(this, &EVRCustomPresenter::onSampleFree) + , m_refCount(1) + , m_renderState(RenderShutdown) + , m_scheduler(this) + , m_tokenCounter(0) + , m_sampleNotify(false) + , m_repaint(false) + , m_prerolled(false) + , m_endStreaming(false) + , m_playbackRate(1.0f) + , m_presentEngine(new D3DPresentEngine) + , m_clock(0) + , m_mixer(0) + , m_mediaEventSink(0) + , m_mediaType(0) + , m_surface(0) + , m_canRenderToSurface(false) + , m_positionOffset(0) +{ + // Initial source rectangle = (0,0,1,1) + m_sourceRect.top = 0; + m_sourceRect.left = 0; + m_sourceRect.bottom = 1; + m_sourceRect.right = 1; + + setSurface(surface); +} + +EVRCustomPresenter::~EVRCustomPresenter() +{ + m_scheduler.flush(); + m_scheduler.stopScheduler(); + m_samplePool.clear(); + + qt_evr_safe_release(&m_clock); + qt_evr_safe_release(&m_mixer); + qt_evr_safe_release(&m_mediaEventSink); + qt_evr_safe_release(&m_mediaType); + + delete m_presentEngine; +} + +HRESULT EVRCustomPresenter::QueryInterface(REFIID riid, void ** ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFGetService) { + *ppvObject = static_cast<IMFGetService*>(this); + } else if (riid == IID_IMFTopologyServiceLookupClient) { + *ppvObject = static_cast<IMFTopologyServiceLookupClient*>(this); + } else if (riid == IID_IMFVideoDeviceID) { + *ppvObject = static_cast<IMFVideoDeviceID*>(this); + } else if (riid == IID_IMFVideoPresenter) { + *ppvObject = static_cast<IMFVideoPresenter*>(this); + } else if (riid == IID_IMFRateSupport) { + *ppvObject = static_cast<IMFRateSupport*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(static_cast<IMFGetService*>(this)); + } else if (riid == IID_IMFClockStateSink) { + *ppvObject = static_cast<IMFClockStateSink*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG EVRCustomPresenter::AddRef() +{ + return InterlockedIncrement(&m_refCount); +} + +ULONG EVRCustomPresenter::Release() +{ + ULONG uCount = InterlockedDecrement(&m_refCount); + if (uCount == 0) + delete this; + return uCount; +} + +HRESULT EVRCustomPresenter::GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject) +{ + HRESULT hr = S_OK; + + if (!ppvObject) + return E_POINTER; + + // The only service GUID that we support is MR_VIDEO_RENDER_SERVICE. + if (guidService != mr_VIDEO_RENDER_SERVICE) + return MF_E_UNSUPPORTED_SERVICE; + + // First try to get the service interface from the D3DPresentEngine object. + hr = m_presentEngine->getService(guidService, riid, ppvObject); + if (FAILED(hr)) + // Next, check if this object supports the interface. + hr = QueryInterface(riid, ppvObject); + + return hr; +} + +HRESULT EVRCustomPresenter::GetDeviceID(IID* deviceID) +{ + if (!deviceID) + return E_POINTER; + + *deviceID = iid_IDirect3DDevice9; + + return S_OK; +} + +HRESULT EVRCustomPresenter::InitServicePointers(IMFTopologyServiceLookup *lookup) +{ + if (!lookup) + return E_POINTER; + + HRESULT hr = S_OK; + DWORD objectCount = 0; + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + // Do not allow initializing when playing or paused. + if (isActive()) + return MF_E_INVALIDREQUEST; + + qt_evr_safe_release(&m_clock); + qt_evr_safe_release(&m_mixer); + qt_evr_safe_release(&m_mediaEventSink); + + // Ask for the clock. Optional, because the EVR might not have a clock. + objectCount = 1; + + lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, + mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_clock), + &objectCount + ); + + // Ask for the mixer. (Required.) + objectCount = 1; + + hr = lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, + mr_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_mixer), + &objectCount + ); + + if (FAILED(hr)) + return hr; + + // Make sure that we can work with this mixer. + hr = configureMixer(m_mixer); + if (FAILED(hr)) + return hr; + + // Ask for the EVR's event-sink interface. (Required.) + objectCount = 1; + + hr = lookup->LookupService(MF_SERVICE_LOOKUP_GLOBAL, 0, + mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_mediaEventSink), + &objectCount + ); + + if (SUCCEEDED(hr)) + m_renderState = RenderStopped; + + return hr; +} + +HRESULT EVRCustomPresenter::ReleaseServicePointers() +{ + // Enter the shut-down state. + m_mutex.lock(); + + m_renderState = RenderShutdown; + + m_mutex.unlock(); + + // Flush any samples that were scheduled. + flush(); + + // Clear the media type and release related resources. + setMediaType(NULL); + + // Release all services that were acquired from InitServicePointers. + qt_evr_safe_release(&m_clock); + qt_evr_safe_release(&m_mixer); + qt_evr_safe_release(&m_mediaEventSink); + + return S_OK; +} + +bool EVRCustomPresenter::isValid() const +{ + return m_presentEngine->isValid() && m_canRenderToSurface; +} + +HRESULT EVRCustomPresenter::ProcessMessage(MFVP_MESSAGE_TYPE message, ULONG_PTR param) +{ + HRESULT hr = S_OK; + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + switch (message) { + // Flush all pending samples. + case MFVP_MESSAGE_FLUSH: + hr = flush(); + break; + + // Renegotiate the media type with the mixer. + case MFVP_MESSAGE_INVALIDATEMEDIATYPE: + hr = renegotiateMediaType(); + break; + + // The mixer received a new input sample. + case MFVP_MESSAGE_PROCESSINPUTNOTIFY: + hr = processInputNotify(); + break; + + // Streaming is about to start. + case MFVP_MESSAGE_BEGINSTREAMING: + hr = beginStreaming(); + break; + + // Streaming has ended. (The EVR has stopped.) + case MFVP_MESSAGE_ENDSTREAMING: + hr = endStreaming(); + break; + + // All input streams have ended. + case MFVP_MESSAGE_ENDOFSTREAM: + // Set the EOS flag. + m_endStreaming = true; + // Check if it's time to send the EC_COMPLETE event to the EVR. + hr = checkEndOfStream(); + break; + + // Frame-stepping is starting. + case MFVP_MESSAGE_STEP: + hr = prepareFrameStep(DWORD(param)); + break; + + // Cancels frame-stepping. + case MFVP_MESSAGE_CANCELSTEP: + hr = cancelFrameStep(); + break; + + default: + hr = E_INVALIDARG; // Unknown message. This case should never occur. + break; + } + + return hr; +} + +HRESULT EVRCustomPresenter::GetCurrentMediaType(IMFVideoMediaType **mediaType) +{ + HRESULT hr = S_OK; + + if (!mediaType) + return E_POINTER; + + *mediaType = NULL; + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + if (!m_mediaType) + return MF_E_NOT_INITIALIZED; + + return m_mediaType->QueryInterface(IID_PPV_ARGS(mediaType)); +} + +HRESULT EVRCustomPresenter::OnClockStart(MFTIME, LONGLONG clockStartOffset) +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + // We cannot start after shutdown. + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + // Check if the clock is already active (not stopped). + if (isActive()) { + m_renderState = RenderStarted; + + // If the clock position changes while the clock is active, it + // is a seek request. We need to flush all pending samples. + if (clockStartOffset != PRESENTATION_CURRENT_POSITION) + flush(); + } else { + m_renderState = RenderStarted; + + // The clock has started from the stopped state. + + // Possibly we are in the middle of frame-stepping OR have samples waiting + // in the frame-step queue. Deal with these two cases first: + hr = startFrameStep(); + if (FAILED(hr)) + return hr; + } + + // Now try to get new output samples from the mixer. + processOutputLoop(); + + return hr; +} + +HRESULT EVRCustomPresenter::OnClockRestart(MFTIME) +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + // The EVR calls OnClockRestart only while paused. + + m_renderState = RenderStarted; + + // Possibly we are in the middle of frame-stepping OR we have samples waiting + // in the frame-step queue. Deal with these two cases first: + hr = startFrameStep(); + if (FAILED(hr)) + return hr; + + // Now resume the presentation loop. + processOutputLoop(); + + return hr; +} + +HRESULT EVRCustomPresenter::OnClockStop(MFTIME) +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + if (m_renderState != RenderStopped) { + m_renderState = RenderStopped; + flush(); + + // If we are in the middle of frame-stepping, cancel it now. + if (m_frameStep.state != FrameStepNone) + cancelFrameStep(); + } + + return S_OK; +} + +HRESULT EVRCustomPresenter::OnClockPause(MFTIME) +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + // We cannot pause the clock after shutdown. + HRESULT hr = checkShutdown(); + + if (SUCCEEDED(hr)) + m_renderState = RenderPaused; + + return hr; +} + +HRESULT EVRCustomPresenter::OnClockSetRate(MFTIME, float rate) +{ + // Note: + // The presenter reports its maximum rate through the IMFRateSupport interface. + // Here, we assume that the EVR honors the maximum rate. + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + // If the rate is changing from zero (scrubbing) to non-zero, cancel the + // frame-step operation. + if ((m_playbackRate == 0.0f) && (rate != 0.0f)) { + cancelFrameStep(); + for (auto sample : qAsConst(m_frameStep.samples)) + sample->Release(); + m_frameStep.samples.clear(); + } + + m_playbackRate = rate; + + // Tell the scheduler about the new rate. + m_scheduler.setClockRate(rate); + + return S_OK; +} + +HRESULT EVRCustomPresenter::GetSlowestRate(MFRATE_DIRECTION, BOOL, float *rate) +{ + if (!rate) + return E_POINTER; + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + HRESULT hr = checkShutdown(); + + if (SUCCEEDED(hr)) { + // There is no minimum playback rate, so the minimum is zero. + *rate = 0; + } + + return S_OK; +} + +HRESULT EVRCustomPresenter::GetFastestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate) +{ + if (!rate) + return E_POINTER; + + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + float maxRate = 0.0f; + + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + // Get the maximum *forward* rate. + maxRate = getMaxRate(thin); + + // For reverse playback, it's the negative of maxRate. + if (direction == MFRATE_REVERSE) + maxRate = -maxRate; + + *rate = maxRate; + + return S_OK; +} + +HRESULT EVRCustomPresenter::IsRateSupported(BOOL thin, float rate, float *nearestSupportedRate) +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + float maxRate = 0.0f; + float nearestRate = rate; // If we support rate, that is the nearest. + + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + return hr; + + // Find the maximum forward rate. + // Note: We have no minimum rate (that is, we support anything down to 0). + maxRate = getMaxRate(thin); + + if (qFabs(rate) > maxRate) { + // The (absolute) requested rate exceeds the maximum rate. + hr = MF_E_UNSUPPORTED_RATE; + + // The nearest supported rate is maxRate. + nearestRate = maxRate; + if (rate < 0) { + // Negative for reverse playback. + nearestRate = -nearestRate; + } + } + + // Return the nearest supported rate. + if (nearestSupportedRate) + *nearestSupportedRate = nearestRate; + + return hr; +} + +void EVRCustomPresenter::supportedFormatsChanged() +{ + const std::lock_guard<QRecursiveMutex> locker(m_mutex); + + m_canRenderToSurface = false; + m_presentEngine->setHint(D3DPresentEngine::RenderToTexture, false); + + // check if we can render to the surface (compatible formats) + if (m_surface) { + QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle); + if (m_presentEngine->supportsTextureRendering() && formats.contains(QVideoFrame::Format_RGB32)) { + m_presentEngine->setHint(D3DPresentEngine::RenderToTexture, true); + m_canRenderToSurface = true; + } else { + formats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::NoHandle); + for (QVideoFrame::PixelFormat format : qAsConst(formats)) { + if (SUCCEEDED(m_presentEngine->checkFormat(qt_evr_D3DFormatFromPixelFormat(format)))) { + m_canRenderToSurface = true; + break; + } + } + } + } + + // TODO: if media type already set, renegotiate? +} + +void EVRCustomPresenter::setSurface(QAbstractVideoSurface *surface) +{ + m_mutex.lock(); + + if (m_surface) { + disconnect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged, + this, &EVRCustomPresenter::supportedFormatsChanged); + } + + m_surface = surface; + + if (m_surface) { + connect(m_surface, &QAbstractVideoSurface::supportedFormatsChanged, + this, &EVRCustomPresenter::supportedFormatsChanged); + } + + m_mutex.unlock(); + + supportedFormatsChanged(); +} + +HRESULT EVRCustomPresenter::configureMixer(IMFTransform *mixer) +{ + // Set the zoom rectangle (ie, the source clipping rectangle). + return setMixerSourceRect(mixer, m_sourceRect); +} + +HRESULT EVRCustomPresenter::renegotiateMediaType() +{ + HRESULT hr = S_OK; + bool foundMediaType = false; + + IMFMediaType *mixerType = NULL; + IMFMediaType *optimalType = NULL; + + if (!m_mixer) + return MF_E_INVALIDREQUEST; + + // Loop through all of the mixer's proposed output types. + DWORD typeIndex = 0; + while (!foundMediaType && (hr != MF_E_NO_MORE_TYPES)) { + qt_evr_safe_release(&mixerType); + qt_evr_safe_release(&optimalType); + + // Step 1. Get the next media type supported by mixer. + hr = m_mixer->GetOutputAvailableType(0, typeIndex++, &mixerType); + if (FAILED(hr)) + break; + + // From now on, if anything in this loop fails, try the next type, + // until we succeed or the mixer runs out of types. + + // Step 2. Check if we support this media type. + if (SUCCEEDED(hr)) + hr = isMediaTypeSupported(mixerType); + + // Step 3. Adjust the mixer's type to match our requirements. + if (SUCCEEDED(hr)) + hr = createOptimalVideoType(mixerType, &optimalType); + + // Step 4. Check if the mixer will accept this media type. + if (SUCCEEDED(hr)) + hr = m_mixer->SetOutputType(0, optimalType, MFT_SET_TYPE_TEST_ONLY); + + // Step 5. Try to set the media type on ourselves. + if (SUCCEEDED(hr)) + hr = setMediaType(optimalType); + + // Step 6. Set output media type on mixer. + if (SUCCEEDED(hr)) { + hr = m_mixer->SetOutputType(0, optimalType, 0); + + // If something went wrong, clear the media type. + if (FAILED(hr)) + setMediaType(NULL); + } + + if (SUCCEEDED(hr)) + foundMediaType = true; + } + + qt_evr_safe_release(&mixerType); + qt_evr_safe_release(&optimalType); + + return hr; +} + +HRESULT EVRCustomPresenter::flush() +{ + m_prerolled = false; + + // The scheduler might have samples that are waiting for + // their presentation time. Tell the scheduler to flush. + + // This call blocks until the scheduler threads discards all scheduled samples. + m_scheduler.flush(); + + // Flush the frame-step queue. + for (auto sample : qAsConst(m_frameStep.samples)) + sample->Release(); + m_frameStep.samples.clear(); + + if (m_renderState == RenderStopped && m_surface && m_surface->isActive()) { + // Repaint with black. + presentSample(NULL); + } + + return S_OK; +} + +HRESULT EVRCustomPresenter::processInputNotify() +{ + HRESULT hr = S_OK; + + // Set the flag that says the mixer has a new sample. + m_sampleNotify = true; + + if (!m_mediaType) { + // We don't have a valid media type yet. + hr = MF_E_TRANSFORM_TYPE_NOT_SET; + } else { + // Try to process an output sample. + processOutputLoop(); + } + return hr; +} + +HRESULT EVRCustomPresenter::beginStreaming() +{ + HRESULT hr = S_OK; + + // Start the scheduler thread. + hr = m_scheduler.startScheduler(m_clock); + + return hr; +} + +HRESULT EVRCustomPresenter::endStreaming() +{ + HRESULT hr = S_OK; + + // Stop the scheduler thread. + hr = m_scheduler.stopScheduler(); + + return hr; +} + +HRESULT EVRCustomPresenter::checkEndOfStream() +{ + if (!m_endStreaming) { + // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message. + return S_OK; + } + + if (m_sampleNotify) { + // The mixer still has input. + return S_OK; + } + + if (m_scheduler.areSamplesScheduled()) { + // Samples are still scheduled for rendering. + return S_OK; + } + + // Everything is complete. Now we can tell the EVR that we are done. + notifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0); + m_endStreaming = false; + + stopSurface(); + return S_OK; +} + +HRESULT EVRCustomPresenter::prepareFrameStep(DWORD steps) +{ + HRESULT hr = S_OK; + + // Cache the step count. + m_frameStep.steps += steps; + + // Set the frame-step state. + m_frameStep.state = FrameStepWaitingStart; + + // If the clock is are already running, we can start frame-stepping now. + // Otherwise, we will start when the clock starts. + if (m_renderState == RenderStarted) + hr = startFrameStep(); + + return hr; +} + +HRESULT EVRCustomPresenter::startFrameStep() +{ + HRESULT hr = S_OK; + IMFSample *sample = NULL; + + if (m_frameStep.state == FrameStepWaitingStart) { + // We have a frame-step request, and are waiting for the clock to start. + // Set the state to "pending," which means we are waiting for samples. + m_frameStep.state = FrameStepPending; + + // If the frame-step queue already has samples, process them now. + while (!m_frameStep.samples.isEmpty() && (m_frameStep.state == FrameStepPending)) { + sample = m_frameStep.samples.takeFirst(); + + hr = deliverFrameStepSample(sample); + if (FAILED(hr)) + goto done; + + qt_evr_safe_release(&sample); + + // We break from this loop when: + // (a) the frame-step queue is empty, or + // (b) the frame-step operation is complete. + } + } else if (m_frameStep.state == FrameStepNone) { + // We are not frame stepping. Therefore, if the frame-step queue has samples, + // we need to process them normally. + while (!m_frameStep.samples.isEmpty()) { + sample = m_frameStep.samples.takeFirst(); + + hr = deliverSample(sample, false); + if (FAILED(hr)) + goto done; + + qt_evr_safe_release(&sample); + } + } + +done: + qt_evr_safe_release(&sample); + return hr; +} + +HRESULT EVRCustomPresenter::completeFrameStep(IMFSample *sample) +{ + HRESULT hr = S_OK; + MFTIME sampleTime = 0; + MFTIME systemTime = 0; + + // Update our state. + m_frameStep.state = FrameStepComplete; + m_frameStep.sampleNoRef = 0; + + // Notify the EVR that the frame-step is complete. + notifyEvent(EC_STEP_COMPLETE, FALSE, 0); // FALSE = completed (not cancelled) + + // If we are scrubbing (rate == 0), also send the "scrub time" event. + if (isScrubbing()) { + // Get the time stamp from the sample. + hr = sample->GetSampleTime(&sampleTime); + if (FAILED(hr)) { + // No time stamp. Use the current presentation time. + if (m_clock) + m_clock->GetCorrelatedTime(0, &sampleTime, &systemTime); + + hr = S_OK; // (Not an error condition.) + } + + notifyEvent(EC_SCRUB_TIME, DWORD(sampleTime), DWORD(((sampleTime) >> 32) & 0xffffffff)); + } + return hr; +} + +HRESULT EVRCustomPresenter::cancelFrameStep() +{ + FrameStepState oldState = m_frameStep.state; + + m_frameStep.state = FrameStepNone; + m_frameStep.steps = 0; + m_frameStep.sampleNoRef = 0; + // Don't clear the frame-step queue yet, because we might frame step again. + + if (oldState > FrameStepNone && oldState < FrameStepComplete) { + // We were in the middle of frame-stepping when it was cancelled. + // Notify the EVR. + notifyEvent(EC_STEP_COMPLETE, TRUE, 0); // TRUE = cancelled + } + return S_OK; +} + +HRESULT EVRCustomPresenter::createOptimalVideoType(IMFMediaType *proposedType, IMFMediaType **optimalType) +{ + HRESULT hr = S_OK; + + RECT rcOutput; + ZeroMemory(&rcOutput, sizeof(rcOutput)); + + MFVideoArea displayArea; + ZeroMemory(&displayArea, sizeof(displayArea)); + + IMFMediaType *mtOptimal = NULL; + + UINT64 size; + int width; + int height; + + // Clone the proposed type. + + hr = MFCreateMediaType(&mtOptimal); + if (FAILED(hr)) + goto done; + + hr = proposedType->CopyAllItems(mtOptimal); + if (FAILED(hr)) + goto done; + + // Modify the new type. + + hr = proposedType->GetUINT64(MF_MT_FRAME_SIZE, &size); + width = int(HI32(size)); + height = int(LO32(size)); + rcOutput.left = 0; + rcOutput.top = 0; + rcOutput.right = width; + rcOutput.bottom = height; + + // Set the geometric aperture, and disable pan/scan. + displayArea = qt_evr_makeMFArea(0, 0, rcOutput.right, rcOutput.bottom); + + hr = mtOptimal->SetUINT32(MF_MT_PAN_SCAN_ENABLED, FALSE); + if (FAILED(hr)) + goto done; + + hr = mtOptimal->SetBlob(MF_MT_GEOMETRIC_APERTURE, reinterpret_cast<UINT8*>(&displayArea), + sizeof(displayArea)); + if (FAILED(hr)) + goto done; + + // Set the pan/scan aperture and the minimum display aperture. We don't care + // about them per se, but the mixer will reject the type if these exceed the + // frame dimentions. + hr = mtOptimal->SetBlob(MF_MT_PAN_SCAN_APERTURE, reinterpret_cast<UINT8*>(&displayArea), + sizeof(displayArea)); + if (FAILED(hr)) + goto done; + + hr = mtOptimal->SetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, reinterpret_cast<UINT8*>(&displayArea), + sizeof(displayArea)); + if (FAILED(hr)) + goto done; + + // Return the pointer to the caller. + *optimalType = mtOptimal; + (*optimalType)->AddRef(); + +done: + qt_evr_safe_release(&mtOptimal); + return hr; + +} + +HRESULT EVRCustomPresenter::setMediaType(IMFMediaType *mediaType) +{ + // Note: mediaType can be NULL (to clear the type) + + // Clearing the media type is allowed in any state (including shutdown). + if (!mediaType) { + stopSurface(); + qt_evr_safe_release(&m_mediaType); + releaseResources(); + return S_OK; + } + + MFRatio fps = { 0, 0 }; + QList<IMFSample*> sampleQueue; + + // Cannot set the media type after shutdown. + HRESULT hr = checkShutdown(); + if (FAILED(hr)) + goto done; + + // Check if the new type is actually different. + // Note: This function safely handles NULL input parameters. + if (qt_evr_areMediaTypesEqual(m_mediaType, mediaType)) + goto done; // Nothing more to do. + + // We're really changing the type. First get rid of the old type. + qt_evr_safe_release(&m_mediaType); + releaseResources(); + + // Initialize the presenter engine with the new media type. + // The presenter engine allocates the samples. + + hr = m_presentEngine->createVideoSamples(mediaType, sampleQueue); + if (FAILED(hr)) + goto done; + + // Mark each sample with our token counter. If this batch of samples becomes + // invalid, we increment the counter, so that we know they should be discarded. + for (auto sample : qAsConst(sampleQueue)) { + hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, m_tokenCounter); + if (FAILED(hr)) + goto done; + } + + // Add the samples to the sample pool. + hr = m_samplePool.initialize(sampleQueue); + if (FAILED(hr)) + goto done; + + // Set the frame rate on the scheduler. + if (SUCCEEDED(qt_evr_getFrameRate(mediaType, &fps)) && (fps.Numerator != 0) && (fps.Denominator != 0)) { + m_scheduler.setFrameRate(fps); + } else { + // NOTE: The mixer's proposed type might not have a frame rate, in which case + // we'll use an arbitrary default. (Although it's unlikely the video source + // does not have a frame rate.) + m_scheduler.setFrameRate(g_DefaultFrameRate); + } + + // Store the media type. + m_mediaType = mediaType; + m_mediaType->AddRef(); + + startSurface(); + +done: + if (FAILED(hr)) + releaseResources(); + return hr; +} + +HRESULT EVRCustomPresenter::isMediaTypeSupported(IMFMediaType *proposed) +{ + D3DFORMAT d3dFormat = D3DFMT_UNKNOWN; + BOOL compressed = FALSE; + MFVideoInterlaceMode interlaceMode = MFVideoInterlace_Unknown; + MFVideoArea videoCropArea; + UINT32 width = 0, height = 0; + + // Validate the format. + HRESULT hr = qt_evr_getFourCC(proposed, reinterpret_cast<DWORD*>(&d3dFormat)); + if (FAILED(hr)) + return hr; + + QVideoFrame::PixelFormat pixelFormat = pixelFormatFromMediaType(proposed); + if (pixelFormat == QVideoFrame::Format_Invalid) + return MF_E_INVALIDMEDIATYPE; + + // When not rendering to texture, only accept pixel formats supported by the video surface + if (!m_presentEngine->isTextureRenderingEnabled() + && m_surface + && !m_surface->supportedPixelFormats().contains(pixelFormat)) { + return MF_E_INVALIDMEDIATYPE; + } + + // Reject compressed media types. + hr = proposed->IsCompressedFormat(&compressed); + if (FAILED(hr)) + return hr; + + if (compressed) + return MF_E_INVALIDMEDIATYPE; + + // The D3DPresentEngine checks whether surfaces can be created using this format + hr = m_presentEngine->checkFormat(d3dFormat); + if (FAILED(hr)) + return hr; + + // Reject interlaced formats. + hr = proposed->GetUINT32(MF_MT_INTERLACE_MODE, reinterpret_cast<UINT32*>(&interlaceMode)); + if (FAILED(hr)) + return hr; + + if (interlaceMode != MFVideoInterlace_Progressive) + return MF_E_INVALIDMEDIATYPE; + + hr = MFGetAttributeSize(proposed, MF_MT_FRAME_SIZE, &width, &height); + if (FAILED(hr)) + return hr; + + // Validate the various apertures (cropping regions) against the frame size. + // Any of these apertures may be unspecified in the media type, in which case + // we ignore it. We just want to reject invalid apertures. + + if (SUCCEEDED(proposed->GetBlob(MF_MT_PAN_SCAN_APERTURE, + reinterpret_cast<UINT8*>(&videoCropArea), + sizeof(videoCropArea), nullptr))) { + hr = qt_evr_validateVideoArea(videoCropArea, width, height); + } + if (SUCCEEDED(proposed->GetBlob(MF_MT_GEOMETRIC_APERTURE, + reinterpret_cast<UINT8*>(&videoCropArea), + sizeof(videoCropArea), nullptr))) { + hr = qt_evr_validateVideoArea(videoCropArea, width, height); + } + if (SUCCEEDED(proposed->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, + reinterpret_cast<UINT8*>(&videoCropArea), + sizeof(videoCropArea), nullptr))) { + hr = qt_evr_validateVideoArea(videoCropArea, width, height); + } + return hr; +} + +void EVRCustomPresenter::processOutputLoop() +{ + HRESULT hr = S_OK; + + // Process as many samples as possible. + while (hr == S_OK) { + // If the mixer doesn't have a new input sample, break from the loop. + if (!m_sampleNotify) { + hr = MF_E_TRANSFORM_NEED_MORE_INPUT; + break; + } + + // Try to process a sample. + hr = processOutput(); + + // NOTE: ProcessOutput can return S_FALSE to indicate it did not + // process a sample. If so, break out of the loop. + } + + if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + // The mixer has run out of input data. Check for end-of-stream. + checkEndOfStream(); + } +} + +HRESULT EVRCustomPresenter::processOutput() +{ + HRESULT hr = S_OK; + DWORD status = 0; + LONGLONG mixerStartTime = 0, mixerEndTime = 0; + MFTIME systemTime = 0; + BOOL repaint = m_repaint; // Temporarily store this state flag. + + MFT_OUTPUT_DATA_BUFFER dataBuffer; + ZeroMemory(&dataBuffer, sizeof(dataBuffer)); + + IMFSample *sample = NULL; + + // If the clock is not running, we present the first sample, + // and then don't present any more until the clock starts. + + if ((m_renderState != RenderStarted) && !m_repaint && m_prerolled) + return S_FALSE; + + // Make sure we have a pointer to the mixer. + if (!m_mixer) + return MF_E_INVALIDREQUEST; + + // Try to get a free sample from the video sample pool. + hr = m_samplePool.getSample(&sample); + if (hr == MF_E_SAMPLEALLOCATOR_EMPTY) // No free samples. Try again when a sample is released. + return S_FALSE; + if (FAILED(hr)) + return hr; + + // From now on, we have a valid video sample pointer, where the mixer will + // write the video data. + + if (m_repaint) { + // Repaint request. Ask the mixer for the most recent sample. + setDesiredSampleTime(sample, m_scheduler.lastSampleTime(), m_scheduler.frameDuration()); + + m_repaint = false; // OK to clear this flag now. + } else { + // Not a repaint request. Clear the desired sample time; the mixer will + // give us the next frame in the stream. + clearDesiredSampleTime(sample); + + if (m_clock) { + // Latency: Record the starting time for ProcessOutput. + m_clock->GetCorrelatedTime(0, &mixerStartTime, &systemTime); + } + } + + // Now we are ready to get an output sample from the mixer. + dataBuffer.dwStreamID = 0; + dataBuffer.pSample = sample; + dataBuffer.dwStatus = 0; + + hr = m_mixer->ProcessOutput(0, 1, &dataBuffer, &status); + + if (FAILED(hr)) { + // Return the sample to the pool. + HRESULT hr2 = m_samplePool.returnSample(sample); + if (FAILED(hr2)) { + hr = hr2; + goto done; + } + // Handle some known error codes from ProcessOutput. + if (hr == MF_E_TRANSFORM_TYPE_NOT_SET) { + // The mixer's format is not set. Negotiate a new format. + hr = renegotiateMediaType(); + } else if (hr == MF_E_TRANSFORM_STREAM_CHANGE) { + // There was a dynamic media type change. Clear our media type. + setMediaType(NULL); + } else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) { + // The mixer needs more input. + // We have to wait for the mixer to get more input. + m_sampleNotify = false; + } + } else { + // We got an output sample from the mixer. + + if (m_clock && !repaint) { + // Latency: Record the ending time for the ProcessOutput operation, + // and notify the EVR of the latency. + + m_clock->GetCorrelatedTime(0, &mixerEndTime, &systemTime); + + LONGLONG latencyTime = mixerEndTime - mixerStartTime; + notifyEvent(EC_PROCESSING_LATENCY, reinterpret_cast<LONG_PTR>(&latencyTime), 0); + } + + // Set up notification for when the sample is released. + hr = trackSample(sample); + if (FAILED(hr)) + goto done; + + // Schedule the sample. + if ((m_frameStep.state == FrameStepNone) || repaint) { + hr = deliverSample(sample, repaint); + if (FAILED(hr)) + goto done; + } else { + // We are frame-stepping (and this is not a repaint request). + hr = deliverFrameStepSample(sample); + if (FAILED(hr)) + goto done; + } + + m_prerolled = true; // We have presented at least one sample now. + } + +done: + qt_evr_safe_release(&sample); + + // Important: Release any events returned from the ProcessOutput method. + qt_evr_safe_release(&dataBuffer.pEvents); + return hr; +} + +HRESULT EVRCustomPresenter::deliverSample(IMFSample *sample, bool repaint) +{ + // If we are not actively playing, OR we are scrubbing (rate = 0) OR this is a + // repaint request, then we need to present the sample immediately. Otherwise, + // schedule it normally. + + bool presentNow = ((m_renderState != RenderStarted) || isScrubbing() || repaint); + + HRESULT hr = m_scheduler.scheduleSample(sample, presentNow); + + if (FAILED(hr)) { + // Notify the EVR that we have failed during streaming. The EVR will notify the + // pipeline. + + notifyEvent(EC_ERRORABORT, hr, 0); + } + + return hr; +} + +HRESULT EVRCustomPresenter::deliverFrameStepSample(IMFSample *sample) +{ + HRESULT hr = S_OK; + IUnknown *unk = NULL; + + // For rate 0, discard any sample that ends earlier than the clock time. + if (isScrubbing() && m_clock && qt_evr_isSampleTimePassed(m_clock, sample)) { + // Discard this sample. + } else if (m_frameStep.state >= FrameStepScheduled) { + // A frame was already submitted. Put this sample on the frame-step queue, + // in case we are asked to step to the next frame. If frame-stepping is + // cancelled, this sample will be processed normally. + sample->AddRef(); + m_frameStep.samples.append(sample); + } else { + // We're ready to frame-step. + + // Decrement the number of steps. + if (m_frameStep.steps > 0) + m_frameStep.steps--; + + if (m_frameStep.steps > 0) { + // This is not the last step. Discard this sample. + } else if (m_frameStep.state == FrameStepWaitingStart) { + // This is the right frame, but the clock hasn't started yet. Put the + // sample on the frame-step queue. When the clock starts, the sample + // will be processed. + sample->AddRef(); + m_frameStep.samples.append(sample); + } else { + // This is the right frame *and* the clock has started. Deliver this sample. + hr = deliverSample(sample, false); + if (FAILED(hr)) + goto done; + + // Query for IUnknown so that we can identify the sample later. + // Per COM rules, an object always returns the same pointer when QI'ed for IUnknown. + hr = sample->QueryInterface(IID_PPV_ARGS(&unk)); + if (FAILED(hr)) + goto done; + + m_frameStep.sampleNoRef = reinterpret_cast<DWORD_PTR>(unk); // No add-ref. + + // NOTE: We do not AddRef the IUnknown pointer, because that would prevent the + // sample from invoking the OnSampleFree callback after the sample is presented. + // We use this IUnknown pointer purely to identify the sample later; we never + // attempt to dereference the pointer. + + m_frameStep.state = FrameStepScheduled; + } + } +done: + qt_evr_safe_release(&unk); + return hr; +} + +HRESULT EVRCustomPresenter::trackSample(IMFSample *sample) +{ + IMFTrackedSample *tracked = NULL; + + HRESULT hr = sample->QueryInterface(IID_PPV_ARGS(&tracked)); + + if (SUCCEEDED(hr)) + hr = tracked->SetAllocator(&m_sampleFreeCB, NULL); + + qt_evr_safe_release(&tracked); + return hr; +} + +void EVRCustomPresenter::releaseResources() +{ + // Increment the token counter to indicate that all existing video samples + // are "stale." As these samples get released, we'll dispose of them. + // + // Note: The token counter is required because the samples are shared + // between more than one thread, and they are returned to the presenter + // through an asynchronous callback (onSampleFree). Without the token, we + // might accidentally re-use a stale sample after the ReleaseResources + // method returns. + + m_tokenCounter++; + + flush(); + + m_samplePool.clear(); + + m_presentEngine->releaseResources(); +} + +HRESULT EVRCustomPresenter::onSampleFree(IMFAsyncResult *result) +{ + IUnknown *object = NULL; + IMFSample *sample = NULL; + IUnknown *unk = NULL; + UINT32 token; + + // Get the sample from the async result object. + HRESULT hr = result->GetObject(&object); + if (FAILED(hr)) + goto done; + + hr = object->QueryInterface(IID_PPV_ARGS(&sample)); + if (FAILED(hr)) + goto done; + + // If this sample was submitted for a frame-step, the frame step operation + // is complete. + + if (m_frameStep.state == FrameStepScheduled) { + // Query the sample for IUnknown and compare it to our cached value. + hr = sample->QueryInterface(IID_PPV_ARGS(&unk)); + if (FAILED(hr)) + goto done; + + if (m_frameStep.sampleNoRef == reinterpret_cast<DWORD_PTR>(unk)) { + // Notify the EVR. + hr = completeFrameStep(sample); + if (FAILED(hr)) + goto done; + } + + // Note: Although object is also an IUnknown pointer, it is not + // guaranteed to be the exact pointer value returned through + // QueryInterface. Therefore, the second QueryInterface call is + // required. + } + + m_mutex.lock(); + + token = MFGetAttributeUINT32(sample, MFSamplePresenter_SampleCounter, (UINT32)-1); + + if (token == m_tokenCounter) { + // Return the sample to the sample pool. + hr = m_samplePool.returnSample(sample); + if (SUCCEEDED(hr)) { + // A free sample is available. Process more data if possible. + processOutputLoop(); + } + } + + m_mutex.unlock(); + +done: + if (FAILED(hr)) + notifyEvent(EC_ERRORABORT, hr, 0); + qt_evr_safe_release(&object); + qt_evr_safe_release(&sample); + qt_evr_safe_release(&unk); + return hr; +} + +float EVRCustomPresenter::getMaxRate(bool thin) +{ + // Non-thinned: + // If we have a valid frame rate and a monitor refresh rate, the maximum + // playback rate is equal to the refresh rate. Otherwise, the maximum rate + // is unbounded (FLT_MAX). + + // Thinned: The maximum rate is unbounded. + + float maxRate = FLT_MAX; + MFRatio fps = { 0, 0 }; + UINT monitorRateHz = 0; + + if (!thin && m_mediaType) { + qt_evr_getFrameRate(m_mediaType, &fps); + monitorRateHz = m_presentEngine->refreshRate(); + + if (fps.Denominator && fps.Numerator && monitorRateHz) { + // Max Rate = Refresh Rate / Frame Rate + maxRate = (float)MulDiv(monitorRateHz, fps.Denominator, fps.Numerator); + } + } + + return maxRate; +} + +bool EVRCustomPresenter::event(QEvent *e) +{ + switch (int(e->type())) { + case StartSurface: + startSurface(); + return true; + case StopSurface: + stopSurface(); + return true; + case PresentSample: + presentSample(static_cast<PresentSampleEvent *>(e)->sample()); + return true; + default: + break; + } + return QObject::event(e); +} + +void EVRCustomPresenter::startSurface() +{ + if (thread() != QThread::currentThread()) { + QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StartSurface))); + return; + } + + if (!m_surface || m_surface->isActive()) + return; + + QVideoSurfaceFormat format = m_presentEngine->videoSurfaceFormat(); + if (!format.isValid()) + return; + + m_surface->start(format); +} + +void EVRCustomPresenter::stopSurface() +{ + if (thread() != QThread::currentThread()) { + QCoreApplication::postEvent(this, new QEvent(QEvent::Type(StopSurface))); + return; + } + + if (!m_surface || !m_surface->isActive()) + return; + + m_surface->stop(); +} + +void EVRCustomPresenter::presentSample(IMFSample *sample) +{ + if (thread() != QThread::currentThread()) { + QCoreApplication::postEvent(this, new PresentSampleEvent(sample)); + return; + } + + if (!m_surface || !m_presentEngine->videoSurfaceFormat().isValid()) + return; + + QVideoFrame frame = m_presentEngine->makeVideoFrame(sample); + + // Since start/end times are related to a position when the clock is started, + // to have times from the beginning, need to adjust it by adding seeked position. + if (m_positionOffset) { + if (frame.startTime()) + frame.setStartTime(frame.startTime() + m_positionOffset); + if (frame.endTime()) + frame.setEndTime(frame.endTime() + m_positionOffset); + } + + if (!m_surface->isActive() || m_surface->surfaceFormat() != m_presentEngine->videoSurfaceFormat()) { + m_surface->stop(); + if (!m_surface->start(m_presentEngine->videoSurfaceFormat())) + return; + } + + m_surface->present(frame); +} + +void EVRCustomPresenter::positionChanged(qint64 position) +{ + m_positionOffset = position * 1000; +} + +HRESULT setDesiredSampleTime(IMFSample *sample, const LONGLONG &sampleTime, const LONGLONG &duration) +{ + if (!sample) + return E_POINTER; + + HRESULT hr = S_OK; + IMFDesiredSample *desired = NULL; + + hr = sample->QueryInterface(IID_PPV_ARGS(&desired)); + if (SUCCEEDED(hr)) + desired->SetDesiredSampleTimeAndDuration(sampleTime, duration); + + qt_evr_safe_release(&desired); + return hr; +} + +HRESULT clearDesiredSampleTime(IMFSample *sample) +{ + if (!sample) + return E_POINTER; + + HRESULT hr = S_OK; + + IMFDesiredSample *desired = NULL; + IUnknown *unkSwapChain = NULL; + + // We store some custom attributes on the sample, so we need to cache them + // and reset them. + // + // This works around the fact that IMFDesiredSample::Clear() removes all of the + // attributes from the sample. + + UINT32 counter = MFGetAttributeUINT32(sample, MFSamplePresenter_SampleCounter, (UINT32)-1); + + hr = sample->QueryInterface(IID_PPV_ARGS(&desired)); + if (SUCCEEDED(hr)) { + desired->Clear(); + + hr = sample->SetUINT32(MFSamplePresenter_SampleCounter, counter); + if (FAILED(hr)) + goto done; + } + +done: + qt_evr_safe_release(&unkSwapChain); + qt_evr_safe_release(&desired); + return hr; +} + +HRESULT setMixerSourceRect(IMFTransform *mixer, const MFVideoNormalizedRect &sourceRect) +{ + if (!mixer) + return E_POINTER; + + IMFAttributes *attributes = NULL; + + HRESULT hr = mixer->GetAttributes(&attributes); + if (SUCCEEDED(hr)) { + hr = attributes->SetBlob(video_ZOOM_RECT, reinterpret_cast<const UINT8*>(&sourceRect), + sizeof(sourceRect)); + attributes->Release(); + } + return hr; +} + +static QVideoFrame::PixelFormat pixelFormatFromMediaType(IMFMediaType *type) +{ + GUID majorType; + if (FAILED(type->GetMajorType(&majorType))) + return QVideoFrame::Format_Invalid; + if (majorType != MFMediaType_Video) + return QVideoFrame::Format_Invalid; + + GUID subtype; + if (FAILED(type->GetGUID(MF_MT_SUBTYPE, &subtype))) + return QVideoFrame::Format_Invalid; + + if (subtype == MFVideoFormat_RGB32) + return QVideoFrame::Format_RGB32; + if (subtype == MFVideoFormat_ARGB32) + return QVideoFrame::Format_ARGB32; + if (subtype == MFVideoFormat_RGB24) + return QVideoFrame::Format_RGB24; + if (subtype == MFVideoFormat_RGB565) + return QVideoFrame::Format_RGB565; + if (subtype == MFVideoFormat_RGB555) + return QVideoFrame::Format_RGB555; + if (subtype == MFVideoFormat_AYUV) + return QVideoFrame::Format_AYUV444; + if (subtype == MFVideoFormat_I420) + return QVideoFrame::Format_YUV420P; + if (subtype == MFVideoFormat_UYVY) + return QVideoFrame::Format_UYVY; + if (subtype == MFVideoFormat_YV12) + return QVideoFrame::Format_YV12; + if (subtype == MFVideoFormat_NV12) + return QVideoFrame::Format_NV12; + + return QVideoFrame::Format_Invalid; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h b/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h new file mode 100644 index 000000000..d60bc4d4c --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrcustompresenter_p.h @@ -0,0 +1,388 @@ +/**************************************************************************** +** +** 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 EVRCUSTOMPRESENTER_H +#define EVRCUSTOMPRESENTER_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 <qmutex.h> +#include <qqueue.h> +#include <qevent.h> +#include <qvideosurfaceformat.h> + +#include "evrdefs_p.h" + +QT_BEGIN_NAMESPACE + +class EVRCustomPresenter; +class D3DPresentEngine; + +class QAbstractVideoSurface; + +template<class T> +class AsyncCallback : public IMFAsyncCallback +{ + Q_DISABLE_COPY(AsyncCallback) +public: + typedef HRESULT (T::*InvokeFn)(IMFAsyncResult *asyncResult); + + AsyncCallback(T *parent, InvokeFn fn) : m_parent(parent), m_invokeFn(fn) + { + } + + // IUnknown + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) override + { + if (!ppv) + return E_POINTER; + + if (iid == __uuidof(IUnknown)) { + *ppv = static_cast<IUnknown*>(static_cast<IMFAsyncCallback*>(this)); + } else if (iid == __uuidof(IMFAsyncCallback)) { + *ppv = static_cast<IMFAsyncCallback*>(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef() override { + // Delegate to parent class. + return m_parent->AddRef(); + } + STDMETHODIMP_(ULONG) Release() override { + // Delegate to parent class. + return m_parent->Release(); + } + + // IMFAsyncCallback methods + STDMETHODIMP GetParameters(DWORD*, DWORD*) override + { + // Implementation of this method is optional. + return E_NOTIMPL; + } + + STDMETHODIMP Invoke(IMFAsyncResult* asyncResult) override + { + return (m_parent->*m_invokeFn)(asyncResult); + } + + T *m_parent; + InvokeFn m_invokeFn; +}; + +class Scheduler +{ + Q_DISABLE_COPY(Scheduler) +public: + enum ScheduleEvent + { + Terminate = WM_USER, + Schedule = WM_USER + 1, + Flush = WM_USER + 2 + }; + + Scheduler(EVRCustomPresenter *presenter); + ~Scheduler(); + + void setFrameRate(const MFRatio &fps); + void setClockRate(float rate) { m_playbackRate = rate; } + + const LONGLONG &lastSampleTime() const { return m_lastSampleTime; } + const LONGLONG &frameDuration() const { return m_perFrameInterval; } + + HRESULT startScheduler(IMFClock *clock); + HRESULT stopScheduler(); + + HRESULT scheduleSample(IMFSample *sample, bool presentNow); + HRESULT processSamplesInQueue(LONG *nextSleep); + HRESULT processSample(IMFSample *sample, LONG *nextSleep); + HRESULT flush(); + + bool areSamplesScheduled(); + + // ThreadProc for the scheduler thread. + static DWORD WINAPI schedulerThreadProc(LPVOID parameter); + +private: + DWORD schedulerThreadProcPrivate(); + + EVRCustomPresenter *m_presenter; + + QQueue<IMFSample*> m_scheduledSamples; // Samples waiting to be presented. + + IMFClock *m_clock; // Presentation clock. Can be NULL. + + DWORD m_threadID; + HANDLE m_schedulerThread; + HANDLE m_threadReadyEvent; + HANDLE m_flushEvent; + + float m_playbackRate; + MFTIME m_perFrameInterval; // Duration of each frame. + LONGLONG m_perFrame_1_4th; // 1/4th of the frame duration. + MFTIME m_lastSampleTime; // Most recent sample time. + + QMutex m_mutex; +}; + +class SamplePool +{ + Q_DISABLE_COPY(SamplePool) +public: + SamplePool(); + ~SamplePool(); + + HRESULT initialize(QList<IMFSample*> &samples); + HRESULT clear(); + + HRESULT getSample(IMFSample **sample); + HRESULT returnSample(IMFSample *sample); + +private: + QMutex m_mutex; + QList<IMFSample*> m_videoSampleQueue; + bool m_initialized; +}; + +class EVRCustomPresenter + : public QObject + , public IMFVideoDeviceID + , public IMFVideoPresenter // Inherits IMFClockStateSink + , public IMFRateSupport + , public IMFGetService + , public IMFTopologyServiceLookupClient +{ + Q_DISABLE_COPY(EVRCustomPresenter) +public: + // Defines the state of the presenter. + enum RenderState + { + RenderStarted = 1, + RenderStopped, + RenderPaused, + RenderShutdown // Initial state. + }; + + // Defines the presenter's state with respect to frame-stepping. + enum FrameStepState + { + FrameStepNone, // Not frame stepping. + FrameStepWaitingStart, // Frame stepping, but the clock is not started. + FrameStepPending, // Clock is started. Waiting for samples. + FrameStepScheduled, // Submitted a sample for rendering. + FrameStepComplete // Sample was rendered. + }; + + enum PresenterEvents + { + StartSurface = QEvent::User, + StopSurface = QEvent::User + 1, + PresentSample = QEvent::User + 2 + }; + + EVRCustomPresenter(QAbstractVideoSurface *surface = 0); + ~EVRCustomPresenter() override; + + bool isValid() const; + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) override; + STDMETHODIMP_(ULONG) AddRef() override; + STDMETHODIMP_(ULONG) Release() override; + + // IMFGetService methods + STDMETHODIMP GetService(REFGUID guidService, REFIID riid, LPVOID *ppvObject) override; + + // IMFVideoPresenter methods + STDMETHODIMP ProcessMessage(MFVP_MESSAGE_TYPE message, ULONG_PTR param) override; + STDMETHODIMP GetCurrentMediaType(IMFVideoMediaType** mediaType) override; + + // IMFClockStateSink methods + STDMETHODIMP OnClockStart(MFTIME systemTime, LONGLONG clockStartOffset) override; + STDMETHODIMP OnClockStop(MFTIME systemTime) override; + STDMETHODIMP OnClockPause(MFTIME systemTime) override; + STDMETHODIMP OnClockRestart(MFTIME systemTime) override; + STDMETHODIMP OnClockSetRate(MFTIME systemTime, float rate) override; + + // IMFRateSupport methods + STDMETHODIMP GetSlowestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate) override; + STDMETHODIMP GetFastestRate(MFRATE_DIRECTION direction, BOOL thin, float *rate) override; + STDMETHODIMP IsRateSupported(BOOL thin, float rate, float *nearestSupportedRate) override; + + // IMFVideoDeviceID methods + STDMETHODIMP GetDeviceID(IID* deviceID) override; + + // IMFTopologyServiceLookupClient methods + STDMETHODIMP InitServicePointers(IMFTopologyServiceLookup *lookup) override; + STDMETHODIMP ReleaseServicePointers() override; + + void supportedFormatsChanged(); + void setSurface(QAbstractVideoSurface *surface); + + void startSurface(); + void stopSurface(); + void presentSample(IMFSample *sample); + + bool event(QEvent *) override; + +public Q_SLOTS: + void positionChanged(qint64 position); + +private: + HRESULT checkShutdown() const + { + if (m_renderState == RenderShutdown) + return MF_E_SHUTDOWN; + else + return S_OK; + } + + // The "active" state is started or paused. + inline bool isActive() const + { + return ((m_renderState == RenderStarted) || (m_renderState == RenderPaused)); + } + + // Scrubbing occurs when the frame rate is 0. + inline bool isScrubbing() const { return m_playbackRate == 0.0f; } + + // Send an event to the EVR through its IMediaEventSink interface. + void notifyEvent(long eventCode, LONG_PTR param1, LONG_PTR param2) + { + if (m_mediaEventSink) + m_mediaEventSink->Notify(eventCode, param1, param2); + } + + float getMaxRate(bool thin); + + // Mixer operations + HRESULT configureMixer(IMFTransform *mixer); + + // Formats + HRESULT createOptimalVideoType(IMFMediaType* proposed, IMFMediaType **optimal); + HRESULT setMediaType(IMFMediaType *mediaType); + HRESULT isMediaTypeSupported(IMFMediaType *mediaType); + + // Message handlers + HRESULT flush(); + HRESULT renegotiateMediaType(); + HRESULT processInputNotify(); + HRESULT beginStreaming(); + HRESULT endStreaming(); + HRESULT checkEndOfStream(); + + // Managing samples + void processOutputLoop(); + HRESULT processOutput(); + HRESULT deliverSample(IMFSample *sample, bool repaint); + HRESULT trackSample(IMFSample *sample); + void releaseResources(); + + // Frame-stepping + HRESULT prepareFrameStep(DWORD steps); + HRESULT startFrameStep(); + HRESULT deliverFrameStepSample(IMFSample *sample); + HRESULT completeFrameStep(IMFSample *sample); + HRESULT cancelFrameStep(); + + // Callback when a video sample is released. + HRESULT onSampleFree(IMFAsyncResult *result); + AsyncCallback<EVRCustomPresenter> m_sampleFreeCB; + + // Holds information related to frame-stepping. + struct FrameStep + { + FrameStepState state = FrameStepNone; + QList<IMFSample*> samples; + DWORD steps = 0; + DWORD_PTR sampleNoRef = 0; + }; + + long m_refCount; + + RenderState m_renderState; + FrameStep m_frameStep; + + QRecursiveMutex m_mutex; + + // Samples and scheduling + Scheduler m_scheduler; // Manages scheduling of samples. + SamplePool m_samplePool; // Pool of allocated samples. + DWORD m_tokenCounter; // Counter. Incremented whenever we create new samples. + + // Rendering state + bool m_sampleNotify; // Did the mixer signal it has an input sample? + bool m_repaint; // Do we need to repaint the last sample? + bool m_prerolled; // Have we presented at least one sample? + bool m_endStreaming; // Did we reach the end of the stream (EOS)? + + MFVideoNormalizedRect m_sourceRect; + float m_playbackRate; + + D3DPresentEngine *m_presentEngine; // Rendering engine. (Never null if the constructor succeeds.) + + IMFClock *m_clock; // The EVR's clock. + IMFTransform *m_mixer; // The EVR's mixer. + IMediaEventSink *m_mediaEventSink; // The EVR's event-sink interface. + IMFMediaType *m_mediaType; // Output media type + + QAbstractVideoSurface *m_surface; + bool m_canRenderToSurface; + qint64 m_positionOffset; // Seek position in microseconds. +}; + +bool qt_evr_setCustomPresenter(IUnknown *evr, EVRCustomPresenter *presenter); + +QT_END_NAMESPACE + +#endif // EVRCUSTOMPRESENTER_H diff --git a/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp b/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp new file mode 100644 index 000000000..8cb6d2593 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrd3dpresentengine.cpp @@ -0,0 +1,412 @@ +/**************************************************************************** +** +** 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 "evrd3dpresentengine_p.h" + +#include "evrhelpers_p.h" + +#include <qabstractvideobuffer.h> +#include <QAbstractVideoSurface> +#include <qvideoframe.h> +#include <QDebug> +#include <qthread.h> +#include <QOffscreenSurface> + +static const int PRESENTER_BUFFER_COUNT = 3; + +QT_BEGIN_NAMESPACE + +class IMFSampleVideoBuffer: public QAbstractVideoBuffer +{ +public: + IMFSampleVideoBuffer(D3DPresentEngine *engine, IMFSample *sample, QAbstractVideoBuffer::HandleType handleType) + : QAbstractVideoBuffer(handleType) + , m_engine(engine) + , m_sample(sample) + , m_surface(0) + , m_mapMode(NotMapped) + { + if (m_sample) { + m_sample->AddRef(); + + IMFMediaBuffer *buffer; + if (SUCCEEDED(m_sample->GetBufferByIndex(0, &buffer))) { + MFGetService(buffer, + mr_BUFFER_SERVICE, + iid_IDirect3DSurface9, + reinterpret_cast<void **>(&m_surface)); + buffer->Release(); + } + } + } + + ~IMFSampleVideoBuffer() override + { + if (m_surface) { + if (m_mapMode != NotMapped) + m_surface->UnlockRect(); + m_surface->Release(); + } + if (m_sample) + m_sample->Release(); + } + + QVariant handle() const override; + + MapMode mapMode() const override { return m_mapMode; } + MapData map(MapMode mode) override; + void unmap() override; + +private: + mutable D3DPresentEngine *m_engine; + IMFSample *m_sample; + IDirect3DSurface9 *m_surface; + MapMode m_mapMode; + mutable unsigned int m_textureId = 0; +}; + +IMFSampleVideoBuffer::MapData IMFSampleVideoBuffer::map(MapMode mode) +{ + if (!m_surface || m_mapMode != NotMapped) + return {}; + + D3DSURFACE_DESC desc; + if (FAILED(m_surface->GetDesc(&desc))) + return {}; + + D3DLOCKED_RECT rect; + if (FAILED(m_surface->LockRect(&rect, NULL, mode == ReadOnly ? D3DLOCK_READONLY : 0))) + return {}; + + m_mapMode = mode; + + MapData mapData; + mapData.nBytes = (int)(rect.Pitch * desc.Height); + mapData.nPlanes = 1; + mapData.bytesPerLine[0] = (int)rect.Pitch; + mapData.data[0] = reinterpret_cast<uchar *>(rect.pBits); + return mapData; +} + +void IMFSampleVideoBuffer::unmap() +{ + if (m_mapMode == NotMapped) + return; + + m_mapMode = NotMapped; + m_surface->UnlockRect(); +} + +QVariant IMFSampleVideoBuffer::handle() const +{ + return m_textureId; +} + + +D3DPresentEngine::D3DPresentEngine() + : m_deviceResetToken(0) + , m_D3D9(0) + , m_device(0) + , m_deviceManager(0) + , m_useTextureRendering(false) +{ + ZeroMemory(&m_displayMode, sizeof(m_displayMode)); + + HRESULT hr = initializeD3D(); + + if (SUCCEEDED(hr)) { + hr = createD3DDevice(); + if (FAILED(hr)) + qWarning("Failed to create D3D device"); + } else { + qWarning("Failed to initialize D3D"); + } +} + +D3DPresentEngine::~D3DPresentEngine() +{ + releaseResources(); + + qt_evr_safe_release(&m_device); + qt_evr_safe_release(&m_deviceManager); + qt_evr_safe_release(&m_D3D9); +} + +HRESULT D3DPresentEngine::initializeD3D() +{ + HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &m_D3D9); + + if (SUCCEEDED(hr)) + hr = DXVA2CreateDirect3DDeviceManager9(&m_deviceResetToken, &m_deviceManager); + + return hr; +} + +HRESULT D3DPresentEngine::createD3DDevice() +{ + HRESULT hr = S_OK; + HWND hwnd = NULL; + UINT uAdapterID = D3DADAPTER_DEFAULT; + DWORD vp = 0; + + D3DCAPS9 ddCaps; + ZeroMemory(&ddCaps, sizeof(ddCaps)); + + IDirect3DDevice9Ex* device = NULL; + + if (!m_D3D9 || !m_deviceManager) + return MF_E_NOT_INITIALIZED; + + hwnd = ::GetShellWindow(); + + D3DPRESENT_PARAMETERS pp; + ZeroMemory(&pp, sizeof(pp)); + + pp.BackBufferWidth = 1; + pp.BackBufferHeight = 1; + pp.BackBufferFormat = D3DFMT_UNKNOWN; + pp.BackBufferCount = 1; + pp.Windowed = TRUE; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.BackBufferFormat = D3DFMT_UNKNOWN; + pp.hDeviceWindow = hwnd; + pp.Flags = D3DPRESENTFLAG_VIDEO; + pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + + hr = m_D3D9->GetDeviceCaps(uAdapterID, D3DDEVTYPE_HAL, &ddCaps); + if (FAILED(hr)) + goto done; + + if (ddCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) + vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; + else + vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + + hr = m_D3D9->CreateDeviceEx( + uAdapterID, + D3DDEVTYPE_HAL, + pp.hDeviceWindow, + vp | D3DCREATE_NOWINDOWCHANGES | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE, + &pp, + NULL, + &device + ); + if (FAILED(hr)) + goto done; + + hr = m_D3D9->GetAdapterDisplayMode(uAdapterID, &m_displayMode); + if (FAILED(hr)) + goto done; + + hr = m_deviceManager->ResetDevice(device, m_deviceResetToken); + if (FAILED(hr)) + goto done; + + qt_evr_safe_release(&m_device); + + m_device = device; + m_device->AddRef(); + +done: + qt_evr_safe_release(&device); + return hr; +} + +bool D3DPresentEngine::isValid() const +{ + return m_device != NULL; +} + +void D3DPresentEngine::releaseResources() +{ + m_surfaceFormat = QVideoSurfaceFormat(); +} + +HRESULT D3DPresentEngine::getService(REFGUID, REFIID riid, void** ppv) +{ + HRESULT hr = S_OK; + + if (riid == __uuidof(IDirect3DDeviceManager9)) { + if (m_deviceManager == NULL) { + hr = MF_E_UNSUPPORTED_SERVICE; + } else { + *ppv = m_deviceManager; + m_deviceManager->AddRef(); + } + } else { + hr = MF_E_UNSUPPORTED_SERVICE; + } + + return hr; +} + +HRESULT D3DPresentEngine::checkFormat(D3DFORMAT format) +{ + if (!m_D3D9 || !m_device) + return E_FAIL; + + HRESULT hr = S_OK; + + D3DDISPLAYMODE mode; + D3DDEVICE_CREATION_PARAMETERS params; + + hr = m_device->GetCreationParameters(¶ms); + if (FAILED(hr)) + return hr; + + UINT uAdapter = params.AdapterOrdinal; + D3DDEVTYPE type = params.DeviceType; + + hr = m_D3D9->GetAdapterDisplayMode(uAdapter, &mode); + if (FAILED(hr)) + return hr; + + hr = m_D3D9->CheckDeviceFormat(uAdapter, type, mode.Format, + D3DUSAGE_RENDERTARGET, + D3DRTYPE_SURFACE, + format); + + if (m_useTextureRendering && format != D3DFMT_X8R8G8B8 && format != D3DFMT_A8R8G8B8) { + // The texture is always in RGB32 so the d3d driver must support conversion from the + // requested format to RGB32. + hr = m_D3D9->CheckDeviceFormatConversion(uAdapter, type, format, D3DFMT_X8R8G8B8); + } + + return hr; +} + +bool D3DPresentEngine::supportsTextureRendering() const +{ + return false; +} + +void D3DPresentEngine::setHint(Hint hint, bool enable) +{ + if (hint == RenderToTexture) + m_useTextureRendering = enable && supportsTextureRendering(); +} + +HRESULT D3DPresentEngine::createVideoSamples(IMFMediaType *format, QList<IMFSample*> &videoSampleQueue) +{ + if (!format) + return MF_E_UNEXPECTED; + + HRESULT hr = S_OK; + + IDirect3DSurface9 *surface = NULL; + IMFSample *videoSample = NULL; + + releaseResources(); + + UINT32 width = 0, height = 0; + hr = MFGetAttributeSize(format, MF_MT_FRAME_SIZE, &width, &height); + if (FAILED(hr)) + return hr; + + DWORD d3dFormat = 0; + hr = qt_evr_getFourCC(format, &d3dFormat); + if (FAILED(hr)) + return hr; + + // Create the video samples. + for (int i = 0; i < PRESENTER_BUFFER_COUNT; i++) { + hr = m_device->CreateRenderTarget(width, height, + (D3DFORMAT)d3dFormat, + D3DMULTISAMPLE_NONE, + 0, + TRUE, + &surface, NULL); + if (FAILED(hr)) + goto done; + + hr = MFCreateVideoSampleFromSurface(surface, &videoSample); + if (FAILED(hr)) + goto done; + + videoSample->AddRef(); + videoSampleQueue.append(videoSample); + + qt_evr_safe_release(&videoSample); + qt_evr_safe_release(&surface); + } + +done: + if (SUCCEEDED(hr)) { + m_surfaceFormat = QVideoSurfaceFormat(QSize(width, height), + m_useTextureRendering ? QVideoFrame::Format_RGB32 + : qt_evr_pixelFormatFromD3DFormat(d3dFormat), + m_useTextureRendering ? QAbstractVideoBuffer::GLTextureHandle + : QAbstractVideoBuffer::NoHandle); + UINT32 horizontal = 1, vertical = 1; + hr = MFGetAttributeRatio(format, MF_MT_PIXEL_ASPECT_RATIO, &horizontal, &vertical); + if (SUCCEEDED(hr)) + m_surfaceFormat.setPixelAspectRatio(horizontal, vertical); + } else { + releaseResources(); + } + + qt_evr_safe_release(&videoSample); + qt_evr_safe_release(&surface); + return hr; +} + +QVideoFrame D3DPresentEngine::makeVideoFrame(IMFSample *sample) +{ + if (!sample) + return QVideoFrame(); + + QVideoFrame frame(new IMFSampleVideoBuffer(this, sample, m_surfaceFormat.handleType()), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat()); + + // WMF uses 100-nanosecond units, Qt uses microseconds + LONGLONG startTime = 0; + auto hr = sample->GetSampleTime(&startTime); + if (SUCCEEDED(hr)) { + frame.setStartTime(startTime * 0.1); + + LONGLONG duration = -1; + if (SUCCEEDED(sample->GetSampleDuration(&duration))) + frame.setEndTime((startTime + duration) * 0.1); + } + + return frame; +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h b/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h new file mode 100644 index 000000000..1e70feadb --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrd3dpresentengine_p.h @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** 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 EVRD3DPRESENTENGINE_H +#define EVRD3DPRESENTENGINE_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 <QMutex> +#include <QVideoSurfaceFormat> + +#include <d3d9.h> + +struct IDirect3D9Ex; +struct IDirect3DDevice9Ex; +struct IDirect3DDeviceManager9; +struct IDirect3DSurface9; +struct IDirect3DTexture9; +struct IMFSample; +struct IMFMediaType; + +// Randomly generated GUIDs +static const GUID MFSamplePresenter_SampleCounter = +{ 0xb0bb83cc, 0xf10f, 0x4e2e, { 0xaa, 0x2b, 0x29, 0xea, 0x5e, 0x92, 0xef, 0x85 } }; + +QT_BEGIN_NAMESPACE + +class QAbstractVideoSurface; + +#ifdef MAYBE_ANGLE + +class OpenGLResources; + +class EGLWrapper +{ + Q_DISABLE_COPY(EGLWrapper) +public: + EGLWrapper(); + + __eglMustCastToProperFunctionPointerType getProcAddress(const char *procname); + EGLSurface createPbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); + EGLBoolean destroySurface(EGLDisplay dpy, EGLSurface surface); + EGLBoolean bindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer); + EGLBoolean releaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer); + +private: + typedef __eglMustCastToProperFunctionPointerType (EGLAPIENTRYP EglGetProcAddress)(const char *procname); + typedef EGLSurface (EGLAPIENTRYP EglCreatePbufferSurface)(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list); + typedef EGLBoolean (EGLAPIENTRYP EglDestroySurface)(EGLDisplay dpy, EGLSurface surface); + typedef EGLBoolean (EGLAPIENTRYP EglBindTexImage)(EGLDisplay dpy, EGLSurface surface, EGLint buffer); + typedef EGLBoolean (EGLAPIENTRYP EglReleaseTexImage)(EGLDisplay dpy, EGLSurface surface, EGLint buffer); + + EglGetProcAddress m_eglGetProcAddress; + EglCreatePbufferSurface m_eglCreatePbufferSurface; + EglDestroySurface m_eglDestroySurface; + EglBindTexImage m_eglBindTexImage; + EglReleaseTexImage m_eglReleaseTexImage; +}; + +#endif // MAYBE_ANGLE + +class D3DPresentEngine +{ + Q_DISABLE_COPY(D3DPresentEngine) +public: + enum Hint + { + RenderToTexture + }; + + D3DPresentEngine(); + virtual ~D3DPresentEngine(); + + bool isValid() const; + void setHint(Hint hint, bool enable = true); + + HRESULT getService(REFGUID guidService, REFIID riid, void** ppv); + HRESULT checkFormat(D3DFORMAT format); + UINT refreshRate() const { return m_displayMode.RefreshRate; } + + bool supportsTextureRendering() const; + bool isTextureRenderingEnabled() const { return m_useTextureRendering; } + + HRESULT createVideoSamples(IMFMediaType *format, QList<IMFSample*>& videoSampleQueue); + QVideoSurfaceFormat videoSurfaceFormat() const { return m_surfaceFormat; } + QVideoFrame makeVideoFrame(IMFSample* sample); + + void releaseResources(); + +private: + HRESULT initializeD3D(); + HRESULT createD3DDevice(); + + + UINT m_deviceResetToken; + D3DDISPLAYMODE m_displayMode; + + IDirect3D9Ex *m_D3D9; + IDirect3DDevice9Ex *m_device; + IDirect3DDeviceManager9 *m_deviceManager; + + QVideoSurfaceFormat m_surfaceFormat; + + bool m_useTextureRendering; + +#ifdef MAYBE_ANGLE + unsigned int updateTexture(IDirect3DSurface9 *src); + + OpenGLResources *m_glResources; + IDirect3DTexture9 *m_texture; +#endif + + friend class IMFSampleVideoBuffer; +}; + +QT_END_NAMESPACE + +#endif // EVRD3DPRESENTENGINE_H diff --git a/src/multimedia/platform/wmf/evr/evrdefs.cpp b/src/multimedia/platform/wmf/evr/evrdefs.cpp new file mode 100644 index 000000000..94370a14a --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrdefs.cpp @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** 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 "evrdefs_p.h" + +const CLSID clsid_EnhancedVideoRenderer = { 0xfa10746c, 0x9b63, 0x4b6c, {0xbc, 0x49, 0xfc, 0x30, 0xe, 0xa5, 0xf2, 0x56} }; +const GUID mr_VIDEO_RENDER_SERVICE = { 0x1092a86c, 0xab1a, 0x459a, {0xa3, 0x36, 0x83, 0x1f, 0xbc, 0x4d, 0x11, 0xff} }; +const GUID mr_VIDEO_MIXER_SERVICE = { 0x73cd2fc, 0x6cf4, 0x40b7, {0x88, 0x59, 0xe8, 0x95, 0x52, 0xc8, 0x41, 0xf8} }; +const GUID mr_BUFFER_SERVICE = { 0xa562248c, 0x9ac6, 0x4ffc, {0x9f, 0xba, 0x3a, 0xf8, 0xf8, 0xad, 0x1a, 0x4d} }; +const GUID video_ZOOM_RECT = { 0x7aaa1638, 0x1b7f, 0x4c93, {0xbd, 0x89, 0x5b, 0x9c, 0x9f, 0xb6, 0xfc, 0xf0} }; +const GUID iid_IDirect3DDevice9 = { 0xd0223b96, 0xbf7a, 0x43fd, {0x92, 0xbd, 0xa4, 0x3b, 0xd, 0x82, 0xb9, 0xeb} }; +const GUID iid_IDirect3DSurface9 = { 0xcfbaf3a, 0x9ff6, 0x429a, {0x99, 0xb3, 0xa2, 0x79, 0x6a, 0xf8, 0xb8, 0x9b} }; diff --git a/src/multimedia/platform/wmf/evr/evrdefs_p.h b/src/multimedia/platform/wmf/evr/evrdefs_p.h new file mode 100644 index 000000000..f9df48387 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrdefs_p.h @@ -0,0 +1,364 @@ +/**************************************************************************** +** +** 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 EVRDEFS_H +#define EVRDEFS_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 <d3d9.h> +#include <evr9.h> +#include <evr.h> +#include <dxva2api.h> +#include <mfapi.h> +#include <mfidl.h> +#include <mferror.h> + +extern const CLSID clsid_EnhancedVideoRenderer; +extern const GUID mr_VIDEO_RENDER_SERVICE; +extern const GUID mr_VIDEO_MIXER_SERVICE; +extern const GUID mr_BUFFER_SERVICE; +extern const GUID video_ZOOM_RECT; +extern const GUID iid_IDirect3DDevice9; +extern const GUID iid_IDirect3DSurface9; + +// The following is required to compile with MinGW + +extern "C" { +HRESULT WINAPI MFCreateVideoSampleFromSurface(IUnknown *pUnkSurface, IMFSample **ppSample); +HRESULT WINAPI Direct3DCreate9Ex(UINT SDKVersion, IDirect3D9Ex**); +} + +#ifndef PRESENTATION_CURRENT_POSITION +#define PRESENTATION_CURRENT_POSITION 0x7fffffffffffffff +#endif + +#ifndef MF_E_SHUTDOWN +#define MF_E_SHUTDOWN ((HRESULT)0xC00D3E85L) +#endif + +#ifndef MF_E_SAMPLEALLOCATOR_EMPTY +#define MF_E_SAMPLEALLOCATOR_EMPTY ((HRESULT)0xC00D4A3EL) +#endif + +#ifndef MF_E_TRANSFORM_STREAM_CHANGE +#define MF_E_TRANSFORM_STREAM_CHANGE ((HRESULT)0xC00D6D61L) +#endif + +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT +#define MF_E_TRANSFORM_NEED_MORE_INPUT ((HRESULT)0xC00D6D72L) +#endif + +#if defined(__GNUC__) && !defined(_MFVideoNormalizedRect_) +#define _MFVideoNormalizedRect_ +typedef struct MFVideoNormalizedRect { + float left; + float top; + float right; + float bottom; +} MFVideoNormalizedRect; +#endif + +#include <initguid.h> + +#ifndef __IMFGetService_INTERFACE_DEFINED__ +#define __IMFGetService_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFGetService, 0xfa993888, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7); +MIDL_INTERFACE("fa993888-4383-415a-a930-dd472a8cf6f7") +IMFGetService : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE GetService(REFGUID, REFIID, LPVOID *) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFGetService, 0xfa993888, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7) +#endif +#endif // __IMFGetService_INTERFACE_DEFINED__ + +#ifndef __IMFVideoDisplayControl_INTERFACE_DEFINED__ +#define __IMFVideoDisplayControl_INTERFACE_DEFINED__ +typedef enum MFVideoAspectRatioMode +{ + MFVideoARMode_None = 0, + MFVideoARMode_PreservePicture = 0x1, + MFVideoARMode_PreservePixel = 0x2, + MFVideoARMode_NonLinearStretch = 0x4, + MFVideoARMode_Mask = 0x7 +} MFVideoAspectRatioMode; + +DEFINE_GUID(IID_IMFVideoDisplayControl, 0xa490b1e4, 0xab84, 0x4d31, 0xa1,0xb2, 0x18,0x1e,0x03,0xb1,0x07,0x7a); +MIDL_INTERFACE("a490b1e4-ab84-4d31-a1b2-181e03b1077a") +IMFVideoDisplayControl : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE GetNativeVideoSize(SIZE *, SIZE *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetIdealVideoSize(SIZE *, SIZE *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetVideoPosition(const MFVideoNormalizedRect *, const LPRECT) = 0; + virtual HRESULT STDMETHODCALLTYPE GetVideoPosition(MFVideoNormalizedRect *, LPRECT) = 0; + virtual HRESULT STDMETHODCALLTYPE SetAspectRatioMode(DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAspectRatioMode(DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetVideoWindow(HWND) = 0; + virtual HRESULT STDMETHODCALLTYPE GetVideoWindow(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE RepaintVideo(void) = 0; + virtual HRESULT STDMETHODCALLTYPE GetCurrentImage(BITMAPINFOHEADER *, BYTE **, DWORD *, LONGLONG *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetBorderColor(COLORREF) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBorderColor(COLORREF *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetRenderingPrefs(DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE GetRenderingPrefs(DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetFullscreen(BOOL) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFullscreen(BOOL *) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFVideoDisplayControl, 0xa490b1e4, 0xab84, 0x4d31, 0xa1,0xb2, 0x18,0x1e,0x03,0xb1,0x07,0x7a) +#endif +#endif // __IMFVideoDisplayControl_INTERFACE_DEFINED__ + +#ifndef __IMFVideoProcessor_INTERFACE_DEFINED__ +#define __IMFVideoProcessor_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFVideoProcessor, 0x6AB0000C, 0xFECE, 0x4d1f, 0xA2,0xAC, 0xA9,0x57,0x35,0x30,0x65,0x6E); +MIDL_INTERFACE("6AB0000C-FECE-4d1f-A2AC-A9573530656E") +IMFVideoProcessor : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE GetAvailableVideoProcessorModes(UINT *, GUID **) = 0; + virtual HRESULT STDMETHODCALLTYPE GetVideoProcessorCaps(LPGUID, DXVA2_VideoProcessorCaps *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetVideoProcessorMode(LPGUID) = 0; + virtual HRESULT STDMETHODCALLTYPE SetVideoProcessorMode(LPGUID) = 0; + virtual HRESULT STDMETHODCALLTYPE GetProcAmpRange(DWORD, DXVA2_ValueRange *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetProcAmpValues(DWORD, DXVA2_ProcAmpValues *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetProcAmpValues(DWORD, DXVA2_ProcAmpValues *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFilteringRange(DWORD, DXVA2_ValueRange *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFilteringValue(DWORD, DXVA2_Fixed32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetFilteringValue(DWORD, DXVA2_Fixed32 *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetBackgroundColor(COLORREF *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetBackgroundColor(COLORREF) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFVideoProcessor, 0x6AB0000C, 0xFECE, 0x4d1f, 0xA2,0xAC, 0xA9,0x57,0x35,0x30,0x65,0x6E) +#endif +#endif // __IMFVideoProcessor_INTERFACE_DEFINED__ + +#ifndef __IMFVideoDeviceID_INTERFACE_DEFINED__ +#define __IMFVideoDeviceID_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFVideoDeviceID, 0xA38D9567, 0x5A9C, 0x4f3c, 0xB2,0x93, 0x8E,0xB4,0x15,0xB2,0x79,0xBA); +MIDL_INTERFACE("A38D9567-5A9C-4f3c-B293-8EB415B279BA") +IMFVideoDeviceID : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetDeviceID(IID *pDeviceID) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFVideoDeviceID, 0xA38D9567, 0x5A9C, 0x4f3c, 0xB2,0x93, 0x8E,0xB4,0x15,0xB2,0x79,0xBA) +#endif +#endif // __IMFVideoDeviceID_INTERFACE_DEFINED__ + +#ifndef __IMFClockStateSink_INTERFACE_DEFINED__ +#define __IMFClockStateSink_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFClockStateSink, 0xF6696E82, 0x74F7, 0x4f3d, 0xA1,0x78, 0x8A,0x5E,0x09,0xC3,0x65,0x9F); +MIDL_INTERFACE("F6696E82-74F7-4f3d-A178-8A5E09C3659F") +IMFClockStateSink : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) = 0; + virtual HRESULT STDMETHODCALLTYPE OnClockStop(MFTIME hnsSystemTime) = 0; + virtual HRESULT STDMETHODCALLTYPE OnClockPause(MFTIME hnsSystemTime) = 0; + virtual HRESULT STDMETHODCALLTYPE OnClockRestart(MFTIME hnsSystemTime) = 0; + virtual HRESULT STDMETHODCALLTYPE OnClockSetRate(MFTIME hnsSystemTime, float flRate) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFClockStateSink, 0xF6696E82, 0x74F7, 0x4f3d, 0xA1,0x78, 0x8A,0x5E,0x09,0xC3,0x65,0x9F) +#endif +#endif // __IMFClockStateSink_INTERFACE_DEFINED__ + +#ifndef __IMFVideoPresenter_INTERFACE_DEFINED__ +#define __IMFVideoPresenter_INTERFACE_DEFINED__ +typedef enum MFVP_MESSAGE_TYPE +{ + MFVP_MESSAGE_FLUSH = 0, + MFVP_MESSAGE_INVALIDATEMEDIATYPE = 0x1, + MFVP_MESSAGE_PROCESSINPUTNOTIFY = 0x2, + MFVP_MESSAGE_BEGINSTREAMING = 0x3, + MFVP_MESSAGE_ENDSTREAMING = 0x4, + MFVP_MESSAGE_ENDOFSTREAM = 0x5, + MFVP_MESSAGE_STEP = 0x6, + MFVP_MESSAGE_CANCELSTEP = 0x7 +} MFVP_MESSAGE_TYPE; + +DEFINE_GUID(IID_IMFVideoPresenter, 0x29AFF080, 0x182A, 0x4a5d, 0xAF,0x3B, 0x44,0x8F,0x3A,0x63,0x46,0xCB); +MIDL_INTERFACE("29AFF080-182A-4a5d-AF3B-448F3A6346CB") +IMFVideoPresenter : public IMFClockStateSink +{ +public: + virtual HRESULT STDMETHODCALLTYPE ProcessMessage(MFVP_MESSAGE_TYPE eMessage, ULONG_PTR ulParam) = 0; + virtual HRESULT STDMETHODCALLTYPE GetCurrentMediaType(IMFVideoMediaType **ppMediaType) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFVideoPresenter, 0x29AFF080, 0x182A, 0x4a5d, 0xAF,0x3B, 0x44,0x8F,0x3A,0x63,0x46,0xCB) +#endif +#endif // __IMFVideoPresenter_INTERFACE_DEFINED__ + +#ifndef __IMFRateSupport_INTERFACE_DEFINED__ +#define __IMFRateSupport_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFRateSupport, 0x0a9ccdbc, 0xd797, 0x4563, 0x96,0x67, 0x94,0xec,0x5d,0x79,0x29,0x2d); +MIDL_INTERFACE("0a9ccdbc-d797-4563-9667-94ec5d79292d") +IMFRateSupport : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetSlowestRate(MFRATE_DIRECTION eDirection, BOOL fThin, float *pflRate) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFastestRate(MFRATE_DIRECTION eDirection, BOOL fThin, float *pflRate) = 0; + virtual HRESULT STDMETHODCALLTYPE IsRateSupported(BOOL fThin, float flRate, float *pflNearestSupportedRate) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFRateSupport, 0x0a9ccdbc, 0xd797, 0x4563, 0x96,0x67, 0x94,0xec,0x5d,0x79,0x29,0x2d) +#endif +#endif // __IMFRateSupport_INTERFACE_DEFINED__ + +#ifndef __IMFTopologyServiceLookup_INTERFACE_DEFINED__ +#define __IMFTopologyServiceLookup_INTERFACE_DEFINED__ +typedef enum _MF_SERVICE_LOOKUP_TYPE +{ + MF_SERVICE_LOOKUP_UPSTREAM = 0, + MF_SERVICE_LOOKUP_UPSTREAM_DIRECT = (MF_SERVICE_LOOKUP_UPSTREAM + 1), + MF_SERVICE_LOOKUP_DOWNSTREAM = (MF_SERVICE_LOOKUP_UPSTREAM_DIRECT + 1), + MF_SERVICE_LOOKUP_DOWNSTREAM_DIRECT = (MF_SERVICE_LOOKUP_DOWNSTREAM + 1), + MF_SERVICE_LOOKUP_ALL = (MF_SERVICE_LOOKUP_DOWNSTREAM_DIRECT + 1), + MF_SERVICE_LOOKUP_GLOBAL = (MF_SERVICE_LOOKUP_ALL + 1) +} MF_SERVICE_LOOKUP_TYPE; + +DEFINE_GUID(IID_IMFTopologyServiceLookup, 0xfa993889, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7); +MIDL_INTERFACE("fa993889-4383-415a-a930-dd472a8cf6f7") +IMFTopologyServiceLookup : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE LookupService(MF_SERVICE_LOOKUP_TYPE Type, + DWORD dwIndex, + REFGUID guidService, + REFIID riid, + LPVOID *ppvObjects, + DWORD *pnObjects) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFTopologyServiceLookup, 0xfa993889, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7) +#endif +#endif // __IMFTopologyServiceLookup_INTERFACE_DEFINED__ + +#ifndef __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__ +#define __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFTopologyServiceLookupClient, 0xfa99388a, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7); +MIDL_INTERFACE("fa99388a-4383-415a-a930-dd472a8cf6f7") +IMFTopologyServiceLookupClient : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE InitServicePointers(IMFTopologyServiceLookup *pLookup) = 0; + virtual HRESULT STDMETHODCALLTYPE ReleaseServicePointers(void) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFTopologyServiceLookupClient, 0xfa99388a, 0x4383, 0x415a, 0xa9,0x30, 0xdd,0x47,0x2a,0x8c,0xf6,0xf7) +#endif +#endif // __IMFTopologyServiceLookupClient_INTERFACE_DEFINED__ + +#ifndef __IMediaEventSink_INTERFACE_DEFINED__ +#define __IMediaEventSink_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMediaEventSink, 0x56a868a2, 0x0ad4, 0x11ce, 0xb0,0x3a, 0x00,0x20,0xaf,0x0b,0xa7,0x70); +MIDL_INTERFACE("56a868a2-0ad4-11ce-b03a-0020af0ba770") +IMediaEventSink : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE Notify(long EventCode, LONG_PTR EventParam1, LONG_PTR EventParam2) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMediaEventSink, 0x56a868a2, 0x0ad4, 0x11ce, 0xb0,0x3a, 0x00,0x20,0xaf,0x0b,0xa7,0x70) +#endif +#endif // __IMediaEventSink_INTERFACE_DEFINED__ + +#ifndef __IMFVideoRenderer_INTERFACE_DEFINED__ +#define __IMFVideoRenderer_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFVideoRenderer, 0xDFDFD197, 0xA9CA, 0x43d8, 0xB3,0x41, 0x6A,0xF3,0x50,0x37,0x92,0xCD); +MIDL_INTERFACE("DFDFD197-A9CA-43d8-B341-6AF3503792CD") +IMFVideoRenderer : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE InitializeRenderer(IMFTransform *pVideoMixer, + IMFVideoPresenter *pVideoPresenter) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFVideoRenderer, 0xDFDFD197, 0xA9CA, 0x43d8, 0xB3,0x41, 0x6A,0xF3,0x50,0x37,0x92,0xCD) +#endif +#endif // __IMFVideoRenderer_INTERFACE_DEFINED__ + +#ifndef __IMFTrackedSample_INTERFACE_DEFINED__ +#define __IMFTrackedSample_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFTrackedSample, 0x245BF8E9, 0x0755, 0x40f7, 0x88,0xA5, 0xAE,0x0F,0x18,0xD5,0x5E,0x17); +MIDL_INTERFACE("245BF8E9-0755-40f7-88A5-AE0F18D55E17") +IMFTrackedSample : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE SetAllocator(IMFAsyncCallback *pSampleAllocator, IUnknown *pUnkState) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFTrackedSample, 0x245BF8E9, 0x0755, 0x40f7, 0x88,0xA5, 0xAE,0x0F,0x18,0xD5,0x5E,0x17) +#endif +#endif // __IMFTrackedSample_INTERFACE_DEFINED__ + +#ifndef __IMFDesiredSample_INTERFACE_DEFINED__ +#define __IMFDesiredSample_INTERFACE_DEFINED__ +DEFINE_GUID(IID_IMFDesiredSample, 0x56C294D0, 0x753E, 0x4260, 0x8D,0x61, 0xA3,0xD8,0x82,0x0B,0x1D,0x54); +MIDL_INTERFACE("56C294D0-753E-4260-8D61-A3D8820B1D54") +IMFDesiredSample : public IUnknown +{ +public: + virtual HRESULT STDMETHODCALLTYPE GetDesiredSampleTimeAndDuration(LONGLONG *phnsSampleTime, + LONGLONG *phnsSampleDuration) = 0; + virtual void STDMETHODCALLTYPE SetDesiredSampleTimeAndDuration(LONGLONG hnsSampleTime, + LONGLONG hnsSampleDuration) = 0; + virtual void STDMETHODCALLTYPE Clear( void) = 0; +}; +#ifdef __CRT_UUID_DECL +__CRT_UUID_DECL(IMFDesiredSample, 0x56C294D0, 0x753E, 0x4260, 0x8D,0x61, 0xA3,0xD8,0x82,0x0B,0x1D,0x54) +#endif +#endif + +#endif // EVRDEFS_H + diff --git a/src/multimedia/platform/wmf/evr/evrhelpers.cpp b/src/multimedia/platform/wmf/evr/evrhelpers.cpp new file mode 100644 index 000000000..aa2311f46 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrhelpers.cpp @@ -0,0 +1,186 @@ +/**************************************************************************** +** +** 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 "evrhelpers_p.h" + +#ifndef D3DFMT_YV12 +#define D3DFMT_YV12 (D3DFORMAT)MAKEFOURCC ('Y', 'V', '1', '2') +#endif +#ifndef D3DFMT_NV12 +#define D3DFMT_NV12 (D3DFORMAT)MAKEFOURCC ('N', 'V', '1', '2') +#endif + +QT_BEGIN_NAMESPACE + +HRESULT qt_evr_getFourCC(IMFMediaType *type, DWORD *fourCC) +{ + if (!fourCC) + return E_POINTER; + + HRESULT hr = S_OK; + GUID guidSubType = GUID_NULL; + + if (SUCCEEDED(hr)) + hr = type->GetGUID(MF_MT_SUBTYPE, &guidSubType); + + if (SUCCEEDED(hr)) + *fourCC = guidSubType.Data1; + + return hr; +} + +bool qt_evr_areMediaTypesEqual(IMFMediaType *type1, IMFMediaType *type2) +{ + if (!type1 && !type2) + return true; + if (!type1 || !type2) + return false; + + DWORD dwFlags = 0; + HRESULT hr = type1->IsEqual(type2, &dwFlags); + + return (hr == S_OK); +} + +HRESULT qt_evr_validateVideoArea(const MFVideoArea& area, UINT32 width, UINT32 height) +{ + float fOffsetX = qt_evr_MFOffsetToFloat(area.OffsetX); + float fOffsetY = qt_evr_MFOffsetToFloat(area.OffsetY); + + if ( ((LONG)fOffsetX + area.Area.cx > (LONG)width) || + ((LONG)fOffsetY + area.Area.cy > (LONG)height) ) { + return MF_E_INVALIDMEDIATYPE; + } + return S_OK; +} + +bool qt_evr_isSampleTimePassed(IMFClock *clock, IMFSample *sample) +{ + if (!sample || !clock) + return false; + + HRESULT hr = S_OK; + MFTIME hnsTimeNow = 0; + MFTIME hnsSystemTime = 0; + MFTIME hnsSampleStart = 0; + MFTIME hnsSampleDuration = 0; + + hr = clock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime); + + if (SUCCEEDED(hr)) + hr = sample->GetSampleTime(&hnsSampleStart); + + if (SUCCEEDED(hr)) + hr = sample->GetSampleDuration(&hnsSampleDuration); + + if (SUCCEEDED(hr)) { + if (hnsSampleStart + hnsSampleDuration < hnsTimeNow) + return true; + } + + return false; +} + +QVideoFrame::PixelFormat qt_evr_pixelFormatFromD3DFormat(DWORD format) +{ + switch (format) { + case D3DFMT_R8G8B8: + return QVideoFrame::Format_RGB24; + case D3DFMT_A8R8G8B8: + return QVideoFrame::Format_ARGB32; + case D3DFMT_X8R8G8B8: + return QVideoFrame::Format_RGB32; + case D3DFMT_R5G6B5: + return QVideoFrame::Format_RGB565; + case D3DFMT_X1R5G5B5: + return QVideoFrame::Format_RGB555; + case D3DFMT_A8: + return QVideoFrame::Format_Y8; + case D3DFMT_A8B8G8R8: + return QVideoFrame::Format_BGRA32; + case D3DFMT_X8B8G8R8: + return QVideoFrame::Format_BGR32; + case D3DFMT_UYVY: + return QVideoFrame::Format_UYVY; + case D3DFMT_YUY2: + return QVideoFrame::Format_YUYV; + case D3DFMT_NV12: + return QVideoFrame::Format_NV12; + case D3DFMT_YV12: + return QVideoFrame::Format_YV12; + case D3DFMT_UNKNOWN: + default: + return QVideoFrame::Format_Invalid; + } +} + +D3DFORMAT qt_evr_D3DFormatFromPixelFormat(QVideoFrame::PixelFormat format) +{ + switch (format) { + case QVideoFrame::Format_RGB24: + return D3DFMT_R8G8B8; + case QVideoFrame::Format_ARGB32: + return D3DFMT_A8R8G8B8; + case QVideoFrame::Format_RGB32: + return D3DFMT_X8R8G8B8; + case QVideoFrame::Format_RGB565: + return D3DFMT_R5G6B5; + case QVideoFrame::Format_RGB555: + return D3DFMT_X1R5G5B5; + case QVideoFrame::Format_Y8: + return D3DFMT_A8; + case QVideoFrame::Format_BGRA32: + return D3DFMT_A8B8G8R8; + case QVideoFrame::Format_BGR32: + return D3DFMT_X8B8G8R8; + case QVideoFrame::Format_UYVY: + return D3DFMT_UYVY; + case QVideoFrame::Format_YUYV: + return D3DFMT_YUY2; + case QVideoFrame::Format_NV12: + return D3DFMT_NV12; + case QVideoFrame::Format_YV12: + return D3DFMT_YV12; + case QVideoFrame::Format_Invalid: + default: + return D3DFMT_UNKNOWN; + } +} + +QT_END_NAMESPACE diff --git a/src/multimedia/platform/wmf/evr/evrhelpers_p.h b/src/multimedia/platform/wmf/evr/evrhelpers_p.h new file mode 100644 index 000000000..89bff6288 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrhelpers_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 EVRHELPERS_H +#define EVRHELPERS_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 "evrdefs_p.h" +#include <qvideoframe.h> + +QT_BEGIN_NAMESPACE + +template<class T> +static inline void qt_evr_safe_release(T **unk) +{ + if (*unk) { + (*unk)->Release(); + *unk = NULL; + } +} + +HRESULT qt_evr_getFourCC(IMFMediaType *type, DWORD *fourCC); + +bool qt_evr_areMediaTypesEqual(IMFMediaType *type1, IMFMediaType *type2); + +HRESULT qt_evr_validateVideoArea(const MFVideoArea& area, UINT32 width, UINT32 height); + +bool qt_evr_isSampleTimePassed(IMFClock *clock, IMFSample *sample); + +inline float qt_evr_MFOffsetToFloat(const MFOffset& offset) +{ + return offset.value + (float(offset.fract) / 65536); +} + +inline MFOffset qt_evr_makeMFOffset(float v) +{ + MFOffset offset; + offset.value = short(v); + offset.fract = WORD(65536 * (v-offset.value)); + return offset; +} + +inline MFVideoArea qt_evr_makeMFArea(float x, float y, DWORD width, DWORD height) +{ + MFVideoArea area; + area.OffsetX = qt_evr_makeMFOffset(x); + area.OffsetY = qt_evr_makeMFOffset(y); + area.Area.cx = width; + area.Area.cy = height; + return area; +} + +inline HRESULT qt_evr_getFrameRate(IMFMediaType *pType, MFRatio *pRatio) +{ + return MFGetAttributeRatio(pType, MF_MT_FRAME_RATE, + reinterpret_cast<UINT32*>(&pRatio->Numerator), + reinterpret_cast<UINT32*>(&pRatio->Denominator)); +} + +QVideoFrame::PixelFormat qt_evr_pixelFormatFromD3DFormat(DWORD format); +D3DFORMAT qt_evr_D3DFormatFromPixelFormat(QVideoFrame::PixelFormat format); + +QT_END_NAMESPACE + +#endif // EVRHELPERS_H + diff --git a/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp new file mode 100644 index 000000000..523dddca8 --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol.cpp @@ -0,0 +1,350 @@ +/**************************************************************************** +** +** 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 "evrvideowindowcontrol_p.h" + +EvrVideoWindowControl::EvrVideoWindowControl(QObject *parent) + : QVideoWindowControl(parent) + , m_windowId(0) + , m_windowColor(RGB(0, 0, 0)) + , m_dirtyValues(0) + , m_aspectRatioMode(Qt::KeepAspectRatio) + , m_brightness(0) + , m_contrast(0) + , m_hue(0) + , m_saturation(0) + , m_fullScreen(false) + , m_displayControl(0) + , m_processor(0) +{ +} + +EvrVideoWindowControl::~EvrVideoWindowControl() +{ + clear(); +} + +bool EvrVideoWindowControl::setEvr(IUnknown *evr) +{ + clear(); + + if (!evr) + return true; + + IMFGetService *service = NULL; + + if (SUCCEEDED(evr->QueryInterface(IID_PPV_ARGS(&service))) + && SUCCEEDED(service->GetService(mr_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_displayControl)))) { + + service->GetService(mr_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_processor)); + + setWinId(m_windowId); + setDisplayRect(m_displayRect); + setAspectRatioMode(m_aspectRatioMode); + m_dirtyValues = DXVA2_ProcAmp_Brightness | DXVA2_ProcAmp_Contrast | DXVA2_ProcAmp_Hue | DXVA2_ProcAmp_Saturation; + applyImageControls(); + } + + if (service) + service->Release(); + + return m_displayControl != NULL; +} + +void EvrVideoWindowControl::clear() +{ + if (m_displayControl) + m_displayControl->Release(); + m_displayControl = NULL; + + if (m_processor) + m_processor->Release(); + m_processor = NULL; +} + +WId EvrVideoWindowControl::winId() const +{ + return m_windowId; +} + +void EvrVideoWindowControl::setWinId(WId id) +{ + m_windowId = id; + + if (m_displayControl) + m_displayControl->SetVideoWindow(HWND(m_windowId)); +} + +QRect EvrVideoWindowControl::displayRect() const +{ + return m_displayRect; +} + +void EvrVideoWindowControl::setDisplayRect(const QRect &rect) +{ + m_displayRect = rect; + + if (m_displayControl) { + RECT displayRect = { rect.left(), rect.top(), rect.right() + 1, rect.bottom() + 1 }; + QSize sourceSize = nativeSize(); + + RECT sourceRect = { 0, 0, sourceSize.width(), sourceSize.height() }; + + if (m_aspectRatioMode == Qt::KeepAspectRatioByExpanding) { + QSize clippedSize = rect.size(); + clippedSize.scale(sourceRect.right, sourceRect.bottom, Qt::KeepAspectRatio); + + sourceRect.left = (sourceRect.right - clippedSize.width()) / 2; + sourceRect.top = (sourceRect.bottom - clippedSize.height()) / 2; + sourceRect.right = sourceRect.left + clippedSize.width(); + sourceRect.bottom = sourceRect.top + clippedSize.height(); + } + + if (sourceSize.width() > 0 && sourceSize.height() > 0) { + MFVideoNormalizedRect sourceNormRect; + sourceNormRect.left = float(sourceRect.left) / float(sourceRect.right); + sourceNormRect.top = float(sourceRect.top) / float(sourceRect.bottom); + sourceNormRect.right = float(sourceRect.right) / float(sourceRect.right); + sourceNormRect.bottom = float(sourceRect.bottom) / float(sourceRect.bottom); + m_displayControl->SetVideoPosition(&sourceNormRect, &displayRect); + } else { + m_displayControl->SetVideoPosition(NULL, &displayRect); + } + + // To refresh content immediately. + repaint(); + } +} + +bool EvrVideoWindowControl::isFullScreen() const +{ + return m_fullScreen; +} + +void EvrVideoWindowControl::setFullScreen(bool fullScreen) +{ + if (m_fullScreen == fullScreen) + return; + emit fullScreenChanged(m_fullScreen = fullScreen); +} + +void EvrVideoWindowControl::repaint() +{ + QSize size = nativeSize(); + if (size.width() > 0 && size.height() > 0 + && m_displayControl + && SUCCEEDED(m_displayControl->RepaintVideo())) { + return; + } + + PAINTSTRUCT paint; + if (HDC dc = ::BeginPaint(HWND(m_windowId), &paint)) { + HPEN pen = ::CreatePen(PS_SOLID, 1, m_windowColor); + HBRUSH brush = ::CreateSolidBrush(m_windowColor); + ::SelectObject(dc, pen); + ::SelectObject(dc, brush); + + ::Rectangle( + dc, + m_displayRect.left(), + m_displayRect.top(), + m_displayRect.right() + 1, + m_displayRect.bottom() + 1); + + ::DeleteObject(pen); + ::DeleteObject(brush); + ::EndPaint(HWND(m_windowId), &paint); + } +} + +QSize EvrVideoWindowControl::nativeSize() const +{ + QSize size; + if (m_displayControl) { + SIZE sourceSize; + if (SUCCEEDED(m_displayControl->GetNativeVideoSize(&sourceSize, 0))) + size = QSize(sourceSize.cx, sourceSize.cy); + } + return size; +} + +Qt::AspectRatioMode EvrVideoWindowControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void EvrVideoWindowControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + m_aspectRatioMode = mode; + + if (m_displayControl) { + switch (mode) { + case Qt::IgnoreAspectRatio: + //comment from MSDN: Do not maintain the aspect ratio of the video. Stretch the video to fit the output rectangle. + m_displayControl->SetAspectRatioMode(MFVideoARMode_None); + break; + case Qt::KeepAspectRatio: + //comment from MSDN: Preserve the aspect ratio of the video by letterboxing or within the output rectangle. + m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture); + break; + case Qt::KeepAspectRatioByExpanding: + //for this mode, more adjustment will be done in setDisplayRect + m_displayControl->SetAspectRatioMode(MFVideoARMode_PreservePicture); + break; + default: + break; + } + setDisplayRect(m_displayRect); + } +} + +int EvrVideoWindowControl::brightness() const +{ + return m_brightness; +} + +void EvrVideoWindowControl::setBrightness(int brightness) +{ + if (m_brightness == brightness) + return; + + m_brightness = brightness; + + m_dirtyValues |= DXVA2_ProcAmp_Brightness; + + applyImageControls(); + + emit brightnessChanged(brightness); +} + +int EvrVideoWindowControl::contrast() const +{ + return m_contrast; +} + +void EvrVideoWindowControl::setContrast(int contrast) +{ + if (m_contrast == contrast) + return; + + m_contrast = contrast; + + m_dirtyValues |= DXVA2_ProcAmp_Contrast; + + applyImageControls(); + + emit contrastChanged(contrast); +} + +int EvrVideoWindowControl::hue() const +{ + return m_hue; +} + +void EvrVideoWindowControl::setHue(int hue) +{ + if (m_hue == hue) + return; + + m_hue = hue; + + m_dirtyValues |= DXVA2_ProcAmp_Hue; + + applyImageControls(); + + emit hueChanged(hue); +} + +int EvrVideoWindowControl::saturation() const +{ + return m_saturation; +} + +void EvrVideoWindowControl::setSaturation(int saturation) +{ + if (m_saturation == saturation) + return; + + m_saturation = saturation; + + m_dirtyValues |= DXVA2_ProcAmp_Saturation; + + applyImageControls(); + + emit saturationChanged(saturation); +} + +void EvrVideoWindowControl::applyImageControls() +{ + if (m_processor) { + DXVA2_ProcAmpValues values; + if (m_dirtyValues & DXVA2_ProcAmp_Brightness) { + values.Brightness = scaleProcAmpValue(DXVA2_ProcAmp_Brightness, m_brightness); + } + if (m_dirtyValues & DXVA2_ProcAmp_Contrast) { + values.Contrast = scaleProcAmpValue(DXVA2_ProcAmp_Contrast, m_contrast); + } + if (m_dirtyValues & DXVA2_ProcAmp_Hue) { + values.Hue = scaleProcAmpValue(DXVA2_ProcAmp_Hue, m_hue); + } + if (m_dirtyValues & DXVA2_ProcAmp_Saturation) { + values.Saturation = scaleProcAmpValue(DXVA2_ProcAmp_Saturation, m_saturation); + } + + if (SUCCEEDED(m_processor->SetProcAmpValues(m_dirtyValues, &values))) { + m_dirtyValues = 0; + } + } +} + +DXVA2_Fixed32 EvrVideoWindowControl::scaleProcAmpValue(DWORD prop, int value) const +{ + float scaledValue = 0.0; + + DXVA2_ValueRange range; + if (SUCCEEDED(m_processor->GetProcAmpRange(prop, &range))) { + scaledValue = DXVA2FixedToFloat(range.DefaultValue); + if (value > 0) + scaledValue += float(value) * (DXVA2FixedToFloat(range.MaxValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100; + else if (value < 0) + scaledValue -= float(value) * (DXVA2FixedToFloat(range.MinValue) - DXVA2FixedToFloat(range.DefaultValue)) / 100; + } + + return DXVA2FloatToFixed(scaledValue); +} diff --git a/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h new file mode 100644 index 000000000..059376f7e --- /dev/null +++ b/src/multimedia/platform/wmf/evr/evrvideowindowcontrol_p.h @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 EVRVIDEOWINDOWCONTROL_H +#define EVRVIDEOWINDOWCONTROL_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 "qvideowindowcontrol.h" + +#include "evrdefs_p.h" + +QT_BEGIN_NAMESPACE + +class EvrVideoWindowControl : public QVideoWindowControl +{ + Q_OBJECT +public: + EvrVideoWindowControl(QObject *parent = 0); + ~EvrVideoWindowControl() override; + + bool setEvr(IUnknown *evr); + + WId winId() const override; + void setWinId(WId id) override; + + QRect displayRect() const override; + void setDisplayRect(const QRect &rect) override; + + bool isFullScreen() const override; + void setFullScreen(bool fullScreen) override; + + void repaint() override; + + QSize nativeSize() const override; + + Qt::AspectRatioMode aspectRatioMode() const override; + void setAspectRatioMode(Qt::AspectRatioMode mode) override; + + int brightness() const override; + void setBrightness(int brightness) override; + + int contrast() const override; + void setContrast(int contrast) override; + + int hue() const override; + void setHue(int hue) override; + + int saturation() const override; + void setSaturation(int saturation) override; + + void applyImageControls(); + +private: + void clear(); + DXVA2_Fixed32 scaleProcAmpValue(DWORD prop, int value) const; + + WId m_windowId; + COLORREF m_windowColor; + DWORD m_dirtyValues; + Qt::AspectRatioMode m_aspectRatioMode; + QRect m_displayRect; + int m_brightness; + int m_contrast; + int m_hue; + int m_saturation; + bool m_fullScreen; + + IMFVideoDisplayControl *m_displayControl; + IMFVideoProcessor *m_processor; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/platform/wmf/mfstream.cpp b/src/multimedia/platform/wmf/mfstream.cpp new file mode 100644 index 000000000..b01dbb7b1 --- /dev/null +++ b/src/multimedia/platform/wmf/mfstream.cpp @@ -0,0 +1,361 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "mfstream_p.h" +#include <QtCore/qcoreapplication.h> + +//MFStream is added for supporting QIODevice type of media source. +//It is used to delegate invocations from media foundation(through IMFByteStream) to QIODevice. + +MFStream::MFStream(QIODevice *stream, bool ownStream) + : m_cRef(1) + , m_stream(stream) + , m_ownStream(ownStream) + , m_currentReadResult(0) +{ + //Move to the thread of the stream object + //to make sure invocations on stream + //are happened in the same thread of stream object + this->moveToThread(stream->thread()); + connect(stream, SIGNAL(readyRead()), this, SLOT(handleReadyRead())); +} + +MFStream::~MFStream() +{ + if (m_currentReadResult) + m_currentReadResult->Release(); + if (m_ownStream) + m_stream->deleteLater(); +} + +//from IUnknown +STDMETHODIMP MFStream::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFByteStream) { + *ppvObject = static_cast<IMFByteStream*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFStream::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFStream::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + this->deleteLater(); + } + return cRef; +} + + +//from IMFByteStream +STDMETHODIMP MFStream::GetCapabilities(DWORD *pdwCapabilities) +{ + if (!pdwCapabilities) + return E_INVALIDARG; + *pdwCapabilities = MFBYTESTREAM_IS_READABLE; + if (!m_stream->isSequential()) + *pdwCapabilities |= MFBYTESTREAM_IS_SEEKABLE; + return S_OK; +} + +STDMETHODIMP MFStream::GetLength(QWORD *pqwLength) +{ + if (!pqwLength) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwLength = QWORD(m_stream->size()); + return S_OK; +} + +STDMETHODIMP MFStream::SetLength(QWORD) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::GetCurrentPosition(QWORD *pqwPosition) +{ + if (!pqwPosition) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pqwPosition = m_stream->pos(); + return S_OK; +} + +STDMETHODIMP MFStream::SetCurrentPosition(QWORD qwPosition) +{ + QMutexLocker locker(&m_mutex); + //SetCurrentPosition may happend during the BeginRead/EndRead pair, + //refusing to execute SetCurrentPosition during that time seems to be + //the simplest workable solution + if (m_currentReadResult) + return S_FALSE; + + bool seekOK = m_stream->seek(qint64(qwPosition)); + if (seekOK) + return S_OK; + else + return S_FALSE; +} + +STDMETHODIMP MFStream::IsEndOfStream(BOOL *pfEndOfStream) +{ + if (!pfEndOfStream) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pfEndOfStream = m_stream->atEnd() ? TRUE : FALSE; + return S_OK; +} + +STDMETHODIMP MFStream::Read(BYTE *pb, ULONG cb, ULONG *pcbRead) +{ + QMutexLocker locker(&m_mutex); + qint64 read = m_stream->read((char*)(pb), qint64(cb)); + if (pcbRead) + *pcbRead = ULONG(read); + return S_OK; +} + +STDMETHODIMP MFStream::BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState) +{ + if (!pCallback || !pb) + return E_INVALIDARG; + + Q_ASSERT(m_currentReadResult == NULL); + + AsyncReadState *state = new (std::nothrow) AsyncReadState(pb, cb); + if (state == NULL) + return E_OUTOFMEMORY; + + HRESULT hr = MFCreateAsyncResult(state, pCallback, punkState, &m_currentReadResult); + state->Release(); + if (FAILED(hr)) + return hr; + + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + return hr; +} + +STDMETHODIMP MFStream::EndRead(IMFAsyncResult* pResult, ULONG *pcbRead) +{ + if (!pcbRead) + return E_INVALIDARG; + IUnknown *pUnk; + pResult->GetObject(&pUnk); + AsyncReadState *state = static_cast<AsyncReadState*>(pUnk); + *pcbRead = state->bytesRead(); + pUnk->Release(); + + m_currentReadResult->Release(); + m_currentReadResult = NULL; + + return S_OK; +} + +STDMETHODIMP MFStream::Write(const BYTE *, ULONG, ULONG *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::BeginWrite(const BYTE *, ULONG , + IMFAsyncCallback *, + IUnknown *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::EndWrite(IMFAsyncResult *, + ULONG *) +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::Seek( + MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, + LONGLONG llSeekOffset, + DWORD, + QWORD *pqwCurrentPosition) +{ + QMutexLocker locker(&m_mutex); + if (m_currentReadResult) + return S_FALSE; + + qint64 pos = qint64(llSeekOffset); + switch (SeekOrigin) { + case msoBegin: + break; + case msoCurrent: + pos += m_stream->pos(); + break; + } + bool seekOK = m_stream->seek(pos); + if (pqwCurrentPosition) + *pqwCurrentPosition = pos; + if (seekOK) + return S_OK; + else + return S_FALSE; +} + +STDMETHODIMP MFStream::Flush() +{ + return E_NOTIMPL; +} + +STDMETHODIMP MFStream::Close() +{ + QMutexLocker locker(&m_mutex); + if (m_ownStream) + m_stream->close(); + return S_OK; +} + +void MFStream::doRead() +{ + bool readDone = true; + IUnknown *pUnk = NULL; + HRESULT hr = m_currentReadResult->GetObject(&pUnk); + if (SUCCEEDED(hr)) { + //do actual read + AsyncReadState *state = static_cast<AsyncReadState*>(pUnk); + ULONG cbRead; + Read(state->pb(), state->cb() - state->bytesRead(), &cbRead); + pUnk->Release(); + + state->setBytesRead(cbRead + state->bytesRead()); + if (state->cb() > state->bytesRead() && !m_stream->atEnd()) { + readDone = false; + } + } + + if (readDone) { + //now inform the original caller + m_currentReadResult->SetStatus(hr); + MFInvokeCallback(m_currentReadResult); + } +} + + +void MFStream::handleReadyRead() +{ + doRead(); +} + +void MFStream::customEvent(QEvent *event) +{ + if (event->type() != QEvent::User) { + QObject::customEvent(event); + return; + } + doRead(); +} + +//AsyncReadState is a helper class used in BeginRead for asynchronous operation +//to record some BeginRead parameters, so these parameters could be +//used later when actually executing the read operation in another thread. +MFStream::AsyncReadState::AsyncReadState(BYTE *pb, ULONG cb) + : m_cRef(1) + , m_pb(pb) + , m_cb(cb) + , m_cbRead(0) +{ +} + +//from IUnknown +STDMETHODIMP MFStream::AsyncReadState::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + + if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFStream::AsyncReadState::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFStream::AsyncReadState::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; +} + +BYTE* MFStream::AsyncReadState::pb() const +{ + return m_pb; +} + +ULONG MFStream::AsyncReadState::cb() const +{ + return m_cb; +} + +ULONG MFStream::AsyncReadState::bytesRead() const +{ + return m_cbRead; +} + +void MFStream::AsyncReadState::setBytesRead(ULONG cbRead) +{ + m_cbRead = cbRead; +} diff --git a/src/multimedia/platform/wmf/mfstream_p.h b/src/multimedia/platform/wmf/mfstream_p.h new file mode 100644 index 000000000..975d02c9d --- /dev/null +++ b/src/multimedia/platform/wmf/mfstream_p.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFSTREAM_H +#define MFSTREAM_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 <mfapi.h> +#include <mfidl.h> +#include <QtCore/qmutex.h> +#include <QtCore/qiodevice.h> +#include <QtCore/qcoreevent.h> + +QT_USE_NAMESPACE + +class MFStream : public QObject, public IMFByteStream +{ + Q_OBJECT +public: + MFStream(QIODevice *stream, bool ownStream); + + ~MFStream(); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + + //from IMFByteStream + STDMETHODIMP GetCapabilities(DWORD *pdwCapabilities); + + STDMETHODIMP GetLength(QWORD *pqwLength); + + STDMETHODIMP SetLength(QWORD); + + STDMETHODIMP GetCurrentPosition(QWORD *pqwPosition); + + STDMETHODIMP SetCurrentPosition(QWORD qwPosition); + + STDMETHODIMP IsEndOfStream(BOOL *pfEndOfStream); + + STDMETHODIMP Read(BYTE *pb, ULONG cb, ULONG *pcbRead); + + STDMETHODIMP BeginRead(BYTE *pb, ULONG cb, IMFAsyncCallback *pCallback, + IUnknown *punkState); + + STDMETHODIMP EndRead(IMFAsyncResult* pResult, ULONG *pcbRead); + + STDMETHODIMP Write(const BYTE *, ULONG, ULONG *); + + STDMETHODIMP BeginWrite(const BYTE *, ULONG , + IMFAsyncCallback *, + IUnknown *); + + STDMETHODIMP EndWrite(IMFAsyncResult *, + ULONG *); + + STDMETHODIMP Seek( + MFBYTESTREAM_SEEK_ORIGIN SeekOrigin, + LONGLONG llSeekOffset, + DWORD, + QWORD *pqwCurrentPosition); + + STDMETHODIMP Flush(); + + STDMETHODIMP Close(); + +private: + class AsyncReadState : public IUnknown + { + public: + AsyncReadState(BYTE *pb, ULONG cb); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + BYTE* pb() const; + ULONG cb() const; + ULONG bytesRead() const; + + void setBytesRead(ULONG cbRead); + + private: + long m_cRef; + BYTE *m_pb; + ULONG m_cb; + ULONG m_cbRead; + }; + + long m_cRef; + QIODevice *m_stream; + bool m_ownStream; + DWORD m_workQueueId; + QMutex m_mutex; + + void doRead(); + +private Q_SLOTS: + void handleReadyRead(); + +protected: + void customEvent(QEvent *event); + IMFAsyncResult *m_currentReadResult; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfactivate.cpp b/src/multimedia/platform/wmf/player/mfactivate.cpp new file mode 100644 index 000000000..05d9321be --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfactivate.cpp @@ -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$ +** +****************************************************************************/ + +#include "mfactivate_p.h" + +#include <mfapi.h> + +MFAbstractActivate::MFAbstractActivate() + : m_attributes(0) + , m_cRef(1) +{ + MFCreateAttributes(&m_attributes, 0); +} + +MFAbstractActivate::~MFAbstractActivate() +{ + if (m_attributes) + m_attributes->Release(); +} + + +HRESULT MFAbstractActivate::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFActivate) { + *ppvObject = static_cast<IMFActivate*>(this); + } else if (riid == IID_IMFAttributes) { + *ppvObject = static_cast<IMFAttributes*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(static_cast<IMFActivate*>(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG MFAbstractActivate::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG MFAbstractActivate::Release(void) +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + return cRef; +} diff --git a/src/multimedia/platform/wmf/player/mfactivate_p.h b/src/multimedia/platform/wmf/player/mfactivate_p.h new file mode 100644 index 000000000..86ef1c438 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfactivate_p.h @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** 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 MFACTIVATE_H +#define MFACTIVATE_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 <mfidl.h> + +class MFAbstractActivate : public IMFActivate +{ +public: + explicit MFAbstractActivate(); + virtual ~MFAbstractActivate(); + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + //from IMFAttributes + STDMETHODIMP GetItem(REFGUID guidKey, PROPVARIANT *pValue) + { + return m_attributes->GetItem(guidKey, pValue); + } + + STDMETHODIMP GetItemType(REFGUID guidKey, MF_ATTRIBUTE_TYPE *pType) + { + return m_attributes->GetItemType(guidKey, pType); + } + + STDMETHODIMP CompareItem(REFGUID guidKey, REFPROPVARIANT Value, BOOL *pbResult) + { + return m_attributes->CompareItem(guidKey, Value, pbResult); + } + + STDMETHODIMP Compare(IMFAttributes *pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL *pbResult) + { + return m_attributes->Compare(pTheirs, MatchType, pbResult); + } + + STDMETHODIMP GetUINT32(REFGUID guidKey, UINT32 *punValue) + { + return m_attributes->GetUINT32(guidKey, punValue); + } + + STDMETHODIMP GetUINT64(REFGUID guidKey, UINT64 *punValue) + { + return m_attributes->GetUINT64(guidKey, punValue); + } + + STDMETHODIMP GetDouble(REFGUID guidKey, double *pfValue) + { + return m_attributes->GetDouble(guidKey, pfValue); + } + + STDMETHODIMP GetGUID(REFGUID guidKey, GUID *pguidValue) + { + return m_attributes->GetGUID(guidKey, pguidValue); + } + + STDMETHODIMP GetStringLength(REFGUID guidKey, UINT32 *pcchLength) + { + return m_attributes->GetStringLength(guidKey, pcchLength); + } + + STDMETHODIMP GetString(REFGUID guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32 *pcchLength) + { + return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); + } + + STDMETHODIMP GetAllocatedString(REFGUID guidKey, LPWSTR *ppwszValue, UINT32 *pcchLength) + { + return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); + } + + STDMETHODIMP GetBlobSize(REFGUID guidKey, UINT32 *pcbBlobSize) + { + return m_attributes->GetBlobSize(guidKey, pcbBlobSize); + } + + STDMETHODIMP GetBlob(REFGUID guidKey, UINT8 *pBuf, UINT32 cbBufSize, UINT32 *pcbBlobSize) + { + return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); + } + + STDMETHODIMP GetAllocatedBlob(REFGUID guidKey, UINT8 **ppBuf, UINT32 *pcbSize) + { + return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); + } + + STDMETHODIMP GetUnknown(REFGUID guidKey, REFIID riid, LPVOID *ppv) + { + return m_attributes->GetUnknown(guidKey, riid, ppv); + } + + STDMETHODIMP SetItem(REFGUID guidKey, REFPROPVARIANT Value) + { + return m_attributes->SetItem(guidKey, Value); + } + + STDMETHODIMP DeleteItem(REFGUID guidKey) + { + return m_attributes->DeleteItem(guidKey); + } + + STDMETHODIMP DeleteAllItems() + { + return m_attributes->DeleteAllItems(); + } + + STDMETHODIMP SetUINT32(REFGUID guidKey, UINT32 unValue) + { + return m_attributes->SetUINT32(guidKey, unValue); + } + + STDMETHODIMP SetUINT64(REFGUID guidKey, UINT64 unValue) + { + return m_attributes->SetUINT64(guidKey, unValue); + } + + STDMETHODIMP SetDouble(REFGUID guidKey, double fValue) + { + return m_attributes->SetDouble(guidKey, fValue); + } + + STDMETHODIMP SetGUID(REFGUID guidKey, REFGUID guidValue) + { + return m_attributes->SetGUID(guidKey, guidValue); + } + + STDMETHODIMP SetString(REFGUID guidKey, LPCWSTR wszValue) + { + return m_attributes->SetString(guidKey, wszValue); + } + + STDMETHODIMP SetBlob(REFGUID guidKey, const UINT8 *pBuf, UINT32 cbBufSize) + { + return m_attributes->SetBlob(guidKey, pBuf, cbBufSize); + } + + STDMETHODIMP SetUnknown(REFGUID guidKey, IUnknown *pUnknown) + { + return m_attributes->SetUnknown(guidKey, pUnknown); + } + + STDMETHODIMP LockStore() + { + return m_attributes->LockStore(); + } + + STDMETHODIMP UnlockStore() + { + return m_attributes->UnlockStore(); + } + + STDMETHODIMP GetCount(UINT32 *pcItems) + { + return m_attributes->GetCount(pcItems); + } + + STDMETHODIMP GetItemByIndex(UINT32 unIndex, GUID *pguidKey, PROPVARIANT *pValue) + { + return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue); + } + + STDMETHODIMP CopyAllItems(IMFAttributes *pDest) + { + return m_attributes->CopyAllItems(pDest); + } + +private: + IMFAttributes *m_attributes; + ULONG m_cRef; +}; + +#endif // MFACTIVATE_H diff --git a/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp new file mode 100644 index 000000000..5e1f130cf --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "QtCore/qdebug.h" +#include "mfaudioendpointcontrol_p.h" + +#include <mmdeviceapi.h> + +MFAudioEndpointControl::MFAudioEndpointControl(QObject *parent) + : QAudioOutputSelectorControl(parent) + , m_currentActivate(0) +{ +} + +MFAudioEndpointControl::~MFAudioEndpointControl() +{ + clear(); +} + +void MFAudioEndpointControl::clear() +{ + m_activeEndpoint.clear(); + + for (auto it = m_devices.cbegin(), end = m_devices.cend(); it != end; ++it) + CoTaskMemFree(it.value()); + + m_devices.clear(); + + if (m_currentActivate) + m_currentActivate->Release(); + m_currentActivate = NULL; +} + +QList<QString> MFAudioEndpointControl::availableOutputs() const +{ + return m_devices.keys(); +} + +QString MFAudioEndpointControl::outputDescription(const QString &name) const +{ + return name.section(QLatin1Char('\\'), -1); +} + +QString MFAudioEndpointControl::defaultOutput() const +{ + return m_defaultEndpoint; +} + +QString MFAudioEndpointControl::activeOutput() const +{ + return m_activeEndpoint; +} + +void MFAudioEndpointControl::setActiveOutput(const QString &name) +{ + if (m_activeEndpoint == name) + return; + QMap<QString, LPWSTR>::iterator it = m_devices.find(name); + if (it == m_devices.end()) + return; + + LPWSTR wstrID = *it; + IMFActivate *activate = NULL; + HRESULT hr = MFCreateAudioRendererActivate(&activate); + if (FAILED(hr)) { + qWarning() << "Failed to create audio renderer activate"; + return; + } + + if (wstrID) { + hr = activate->SetString(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ID, wstrID); + } else { + //This is the default one that has been inserted in updateEndpoints(), + //so give the activate a hint that we want to use the device for multimedia playback + //then the media foundation will choose an appropriate one. + + //from MSDN: + //The ERole enumeration defines constants that indicate the role that the system has assigned to an audio endpoint device. + //eMultimedia: Music, movies, narration, and live music recording. + hr = activate->SetUINT32(MF_AUDIO_RENDERER_ATTRIBUTE_ENDPOINT_ROLE, eMultimedia); + } + + if (FAILED(hr)) { + qWarning() << "Failed to set attribute for audio device" << name; + return; + } + + if (m_currentActivate) + m_currentActivate->Release(); + m_currentActivate = activate; + m_activeEndpoint = name; +} + +IMFActivate* MFAudioEndpointControl::createActivate() +{ + clear(); + + updateEndpoints(); + + // Check if an endpoint is available ("Default" is always inserted) + if (m_devices.count() <= 1) + return NULL; + + setActiveOutput(m_defaultEndpoint); + + return m_currentActivate; +} + +void MFAudioEndpointControl::updateEndpoints() +{ + m_defaultEndpoint = QString::fromLatin1("Default"); + m_devices.insert(m_defaultEndpoint, NULL); + + IMMDeviceEnumerator *pEnum = NULL; + HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), + NULL, CLSCTX_ALL, + __uuidof(IMMDeviceEnumerator), + (void**)&pEnum); + if (SUCCEEDED(hr)) { + IMMDeviceCollection *pDevices = NULL; + hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices); + if (SUCCEEDED(hr)) { + UINT count; + hr = pDevices->GetCount(&count); + if (SUCCEEDED(hr)) { + for (UINT i = 0; i < count; ++i) { + IMMDevice *pDevice = NULL; + hr = pDevices->Item(i, &pDevice); + if (SUCCEEDED(hr)) { + LPWSTR wstrID = NULL; + hr = pDevice->GetId(&wstrID); + if (SUCCEEDED(hr)) { + QString deviceId = QString::fromWCharArray(wstrID); + m_devices.insert(deviceId, wstrID); + } + pDevice->Release(); + } + } + } + pDevices->Release(); + } + pEnum->Release(); + } +} diff --git a/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h new file mode 100644 index 000000000..21d404104 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfaudioendpointcontrol_p.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFAUDIOENDPOINTCONTROL_H +#define MFAUDIOENDPOINTCONTROL_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 <mfapi.h> +#include <mfidl.h> + +#include "qaudiooutputselectorcontrol.h" + +class MFPlayerService; + +QT_USE_NAMESPACE + +class MFAudioEndpointControl : public QAudioOutputSelectorControl +{ + Q_OBJECT +public: + MFAudioEndpointControl(QObject *parent = 0); + ~MFAudioEndpointControl(); + + QList<QString> availableOutputs() const; + + QString outputDescription(const QString &name) const; + + QString defaultOutput() const; + QString activeOutput() const; + + void setActiveOutput(const QString& name); + + IMFActivate* createActivate(); + +private: + void clear(); + void updateEndpoints(); + + QString m_defaultEndpoint; + QString m_activeEndpoint; + QMap<QString, LPWSTR> m_devices; + IMFActivate *m_currentActivate; + +}; + +#endif + diff --git a/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp b/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp new file mode 100644 index 000000000..a371629da --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfaudioprobecontrol.cpp @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** 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 "mfaudioprobecontrol_p.h" + +MFAudioProbeControl::MFAudioProbeControl(QObject *parent): + QMediaAudioProbeControl(parent) +{ +} + +MFAudioProbeControl::~MFAudioProbeControl() +{ +} + +void MFAudioProbeControl::bufferProbed(const char *data, quint32 size, const QAudioFormat& format, qint64 startTime) +{ + if (!format.isValid()) + return; + + QAudioBuffer audioBuffer = QAudioBuffer(QByteArray(data, size), format, startTime); + + { + QMutexLocker locker(&m_bufferMutex); + m_pendingBuffer = audioBuffer; + QMetaObject::invokeMethod(this, "bufferProbed", Qt::QueuedConnection); + } +} + +void MFAudioProbeControl::bufferProbed() +{ + QAudioBuffer audioBuffer; + { + QMutexLocker locker(&m_bufferMutex); + if (!m_pendingBuffer.isValid()) + return; + audioBuffer = m_pendingBuffer; + } + emit audioBufferProbed(audioBuffer); +} diff --git a/src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h b/src/multimedia/platform/wmf/player/mfaudioprobecontrol_p.h new file mode 100644 index 000000000..0ccf151ad --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfaudioprobecontrol_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 MFAUDIOPROBECONTROL_H +#define MFAUDIOPROBECONTROL_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 <qmediaaudioprobecontrol.h> +#include <QtCore/qmutex.h> +#include <qaudiobuffer.h> + +QT_USE_NAMESPACE + +class MFAudioProbeControl : public QMediaAudioProbeControl +{ + Q_OBJECT +public: + explicit MFAudioProbeControl(QObject *parent); + virtual ~MFAudioProbeControl(); + + void bufferProbed(const char *data, quint32 size, const QAudioFormat& format, qint64 startTime); + +private slots: + void bufferProbed(); + +private: + QAudioBuffer m_pendingBuffer; + QMutex m_bufferMutex; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp new file mode 100644 index 000000000..24c176c24 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 "mfevrvideowindowcontrol_p.h" + +#include <qdebug.h> + +MFEvrVideoWindowControl::MFEvrVideoWindowControl(QObject *parent) + : EvrVideoWindowControl(parent) + , m_currentActivate(NULL) + , m_evrSink(NULL) +{ +} + +MFEvrVideoWindowControl::~MFEvrVideoWindowControl() +{ + clear(); +} + +void MFEvrVideoWindowControl::clear() +{ + setEvr(NULL); + + if (m_evrSink) + m_evrSink->Release(); + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } + m_evrSink = NULL; + m_currentActivate = NULL; +} + +IMFActivate* MFEvrVideoWindowControl::createActivate() +{ + clear(); + + if (FAILED(MFCreateVideoRendererActivate(0, &m_currentActivate))) { + qWarning() << "Failed to create evr video renderer activate!"; + return NULL; + } + if (FAILED(m_currentActivate->ActivateObject(IID_IMFMediaSink, (LPVOID*)(&m_evrSink)))) { + qWarning() << "Failed to activate evr media sink!"; + return NULL; + } + if (!setEvr(m_evrSink)) + return NULL; + + return m_currentActivate; +} + +void MFEvrVideoWindowControl::releaseActivate() +{ + clear(); +} diff --git a/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_p.h new file mode 100644 index 000000000..c74148431 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfevrvideowindowcontrol_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 MFEVRVIDEOWINDOWCONTROL_H +#define MFEVRVIDEOWINDOWCONTROL_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/evrvideowindowcontrol_p.h" + +QT_USE_NAMESPACE + +class MFEvrVideoWindowControl : public EvrVideoWindowControl +{ +public: + MFEvrVideoWindowControl(QObject *parent = 0); + ~MFEvrVideoWindowControl(); + + IMFActivate* createActivate(); + void releaseActivate(); + +private: + void clear(); + + IMFActivate *m_currentActivate; + IMFMediaSink *m_evrSink; +}; + +#endif // MFEVRVIDEOWINDOWCONTROL_H diff --git a/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp b/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp new file mode 100644 index 000000000..b7cf771e8 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfmetadatacontrol.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 <qmediametadata.h> +#include <qdatetime.h> +#include <qimage.h> + +#include "mfmetadatacontrol_p.h" +#include "mfplayerservice_p.h" +#include "Propkey.h" + +//#define DEBUG_MEDIAFOUNDATION + +static QString nameForGUID(GUID guid) +{ + // Audio formats + if (guid == MFAudioFormat_AAC) + return QStringLiteral("MPEG AAC Audio"); + else if (guid == MFAudioFormat_ADTS) + return QStringLiteral("MPEG ADTS AAC Audio"); + else if (guid == MFAudioFormat_Dolby_AC3_SPDIF) + return QStringLiteral("Dolby AC-3 SPDIF"); + else if (guid == MFAudioFormat_DRM) + return QStringLiteral("DRM"); + else if (guid == MFAudioFormat_DTS) + return QStringLiteral("Digital Theater Systems Audio (DTS)"); + else if (guid == MFAudioFormat_Float) + return QStringLiteral("IEEE Float Audio"); + else if (guid == MFAudioFormat_MP3) + return QStringLiteral("MPEG Audio Layer-3 (MP3)"); + else if (guid == MFAudioFormat_MPEG) + return QStringLiteral("MPEG-1 Audio"); + else if (guid == MFAudioFormat_MSP1) + return QStringLiteral("Windows Media Audio Voice"); + else if (guid == MFAudioFormat_PCM) + return QStringLiteral("Uncompressed PCM Audio"); + else if (guid == MFAudioFormat_WMASPDIF) + return QStringLiteral("Windows Media Audio 9 SPDIF"); + else if (guid == MFAudioFormat_WMAudioV8) + return QStringLiteral("Windows Media Audio 8 (WMA2)"); + else if (guid == MFAudioFormat_WMAudioV9) + return QStringLiteral("Windows Media Audio 9 (WMA3"); + else if (guid == MFAudioFormat_WMAudio_Lossless) + return QStringLiteral("Windows Media Audio 9 Lossless"); + + // Video formats + if (guid == MFVideoFormat_DV25) + return QStringLiteral("DVCPRO 25 (DV25)"); + else if (guid == MFVideoFormat_DV50) + return QStringLiteral("DVCPRO 50 (DV50)"); + else if (guid == MFVideoFormat_DVC) + return QStringLiteral("DVC/DV Video"); + else if (guid == MFVideoFormat_DVH1) + return QStringLiteral("DVCPRO 100 (DVH1)"); + else if (guid == MFVideoFormat_DVHD) + return QStringLiteral("HD-DVCR (DVHD)"); + else if (guid == MFVideoFormat_DVSD) + return QStringLiteral("SDL-DVCR (DVSD)"); + else if (guid == MFVideoFormat_DVSL) + return QStringLiteral("SD-DVCR (DVSL)"); + else if (guid == MFVideoFormat_H264) + return QStringLiteral("H.264 Video"); + else if (guid == MFVideoFormat_M4S2) + return QStringLiteral("MPEG-4 part 2 Video (M4S2)"); + else if (guid == MFVideoFormat_MJPG) + return QStringLiteral("Motion JPEG (MJPG)"); + else if (guid == MFVideoFormat_MP43) + return QStringLiteral("Microsoft MPEG 4 version 3 (MP43)"); + else if (guid == MFVideoFormat_MP4S) + return QStringLiteral("ISO MPEG 4 version 1 (MP4S)"); + else if (guid == MFVideoFormat_MP4V) + return QStringLiteral("MPEG-4 part 2 Video (MP4V)"); + else if (guid == MFVideoFormat_MPEG2) + return QStringLiteral("MPEG-2 Video"); + else if (guid == MFVideoFormat_MPG1) + return QStringLiteral("MPEG-1 Video"); + else if (guid == MFVideoFormat_MSS1) + return QStringLiteral("Windows Media Screen 1 (MSS1)"); + else if (guid == MFVideoFormat_MSS2) + return QStringLiteral("Windows Media Video 9 Screen (MSS2)"); + else if (guid == MFVideoFormat_WMV1) + return QStringLiteral("Windows Media Video 7 (WMV1)"); + else if (guid == MFVideoFormat_WMV2) + return QStringLiteral("Windows Media Video 8 (WMV2)"); + else if (guid == MFVideoFormat_WMV3) + return QStringLiteral("Windows Media Video 9 (WMV3)"); + else if (guid == MFVideoFormat_WVC1) + return QStringLiteral("Windows Media Video VC1 (WVC1)"); + + else + return QStringLiteral("Unknown codec"); +} + +MFMetaDataControl::MFMetaDataControl(QObject *parent) + : QMetaDataReaderControl(parent) + , m_metaData(0) + , m_content(0) +{ +} + +MFMetaDataControl::~MFMetaDataControl() +{ + if (m_metaData) + m_metaData->Release(); + if (m_content) + m_content->Release(); +} + +bool MFMetaDataControl::isMetaDataAvailable() const +{ + return m_content || m_metaData; +} + +QVariant MFMetaDataControl::metaData(const QString &key) const +{ + QVariant value; + if (!isMetaDataAvailable()) + return value; + + int index = m_availableMetaDatas.indexOf(key); + if (index < 0) + return value; + + PROPVARIANT var; + PropVariantInit(&var); + HRESULT hr = S_FALSE; + if (m_content) + hr = m_content->GetValue(m_commonKeys[index], &var); + else if (m_metaData) + hr = m_metaData->GetProperty(reinterpret_cast<LPCWSTR>(m_commonNames[index].utf16()), &var); + + if (SUCCEEDED(hr)) { + value = convertValue(var); + + // some metadata needs to be reformatted + if (value.isValid() && m_content) { + if (key == QMediaMetaData::MediaType) { + QString v = value.toString(); + if (v == QLatin1String("{D1607DBC-E323-4BE2-86A1-48A42A28441E}")) + value = QStringLiteral("Music"); + else if (v == QLatin1String("{DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B}")) + value = QStringLiteral("Video"); + else if (v == QLatin1String("{01CD0F29-DA4E-4157-897B-6275D50C4F11}")) + value = QStringLiteral("Audio"); + else if (v == QLatin1String("{FCF24A76-9A57-4036-990D-E35DD8B244E1}")) + value = QStringLiteral("Other"); + } else if (key == QMediaMetaData::Duration) { + // duration is provided in 100-nanosecond units, convert to milliseconds + value = (value.toLongLong() + 10000) / 10000; + } else if (key == QMediaMetaData::AudioCodec || key == QMediaMetaData::VideoCodec) { + GUID guid; + if (SUCCEEDED(CLSIDFromString((const WCHAR*)value.toString().utf16(), &guid))) + value = nameForGUID(guid); + } else if (key == QMediaMetaData::Resolution) { + QSize res; + res.setHeight(value.toUInt()); + if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_FrameWidth, &var))) + res.setWidth(convertValue(var).toUInt()); + value = res; + } else if (key == QMediaMetaData::Orientation) { + uint orientation = 0; + if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_Orientation, &var))) + orientation = convertValue(var).toUInt(); + value = orientation; + } else if (key == QMediaMetaData::PixelAspectRatio) { + QSize aspectRatio; + aspectRatio.setWidth(value.toUInt()); + if (m_content && SUCCEEDED(m_content->GetValue(PKEY_Video_VerticalAspectRatio, &var))) + aspectRatio.setHeight(convertValue(var).toUInt()); + value = aspectRatio; + } else if (key == QMediaMetaData::VideoFrameRate) { + value = value.toReal() / 1000.f; + } + } + } + + PropVariantClear(&var); + return value; +} + +QVariant MFMetaDataControl::convertValue(const PROPVARIANT& var) const +{ + QVariant value; + switch (var.vt) { + case VT_LPWSTR: + value = QString::fromUtf16(reinterpret_cast<const ushort*>(var.pwszVal)); + break; + case VT_UI4: + value = uint(var.ulVal); + break; + case VT_UI8: + value = qulonglong(var.uhVal.QuadPart); + break; + case VT_BOOL: + value = bool(var.boolVal); + break; + case VT_FILETIME: + SYSTEMTIME sysDate; + if (!FileTimeToSystemTime(&var.filetime, &sysDate)) + break; + value = QDate(sysDate.wYear, sysDate.wMonth, sysDate.wDay); + break; + case VT_STREAM: + { + STATSTG stat; + if (FAILED(var.pStream->Stat(&stat, STATFLAG_NONAME))) + break; + void *data = malloc(stat.cbSize.QuadPart); + ULONG read = 0; + if (FAILED(var.pStream->Read(data, stat.cbSize.QuadPart, &read))) { + free(data); + break; + } + value = QImage::fromData((const uchar*)data, read); + free(data); + } + break; + case VT_VECTOR | VT_LPWSTR: + QStringList vList; + for (ULONG i = 0; i < var.calpwstr.cElems; ++i) + vList.append(QString::fromUtf16(reinterpret_cast<const ushort*>(var.calpwstr.pElems[i]))); + value = vList; + break; + } + return value; +} + +QStringList MFMetaDataControl::availableMetaData() const +{ + return m_availableMetaDatas; +} + +void MFMetaDataControl::updateSource(IMFPresentationDescriptor* sourcePD, IMFMediaSource* mediaSource) +{ + if (m_metaData) { + m_metaData->Release(); + m_metaData = 0; + } + + if (m_content) { + m_content->Release(); + m_content = 0; + } + + m_availableMetaDatas.clear(); + m_commonKeys.clear(); + m_commonNames.clear(); + + if (SUCCEEDED(MFGetService(mediaSource, MF_PROPERTY_HANDLER_SERVICE, IID_PPV_ARGS(&m_content)))) { + DWORD cProps; + if (SUCCEEDED(m_content->GetCount(&cProps))) { + for (DWORD i = 0; i < cProps; i++) + { + PROPERTYKEY key; + if (FAILED(m_content->GetAt(i, &key))) + continue; + bool common = true; + if (key == PKEY_Author) { + m_availableMetaDatas.push_back(QMediaMetaData::Author); + } else if (key == PKEY_Title) { + m_availableMetaDatas.push_back(QMediaMetaData::Title); + } else if (key == PKEY_Media_SubTitle) { + m_availableMetaDatas.push_back(QMediaMetaData::SubTitle); + } else if (key == PKEY_ParentalRating) { + m_availableMetaDatas.push_back(QMediaMetaData::ParentalRating); + } else if (key == PKEY_Media_EncodingSettings) { + m_availableMetaDatas.push_back(QMediaMetaData::Description); + } else if (key == PKEY_Copyright) { + m_availableMetaDatas.push_back(QMediaMetaData::Copyright); + } else if (key == PKEY_Comment) { + m_availableMetaDatas.push_back(QMediaMetaData::Comment); + } else if (key == PKEY_Media_ProviderStyle) { + m_availableMetaDatas.push_back(QMediaMetaData::Genre); + } else if (key == PKEY_Media_Year) { + m_availableMetaDatas.push_back(QMediaMetaData::Year); + } else if (key == PKEY_Media_DateEncoded) { + m_availableMetaDatas.push_back(QMediaMetaData::Date); + } else if (key == PKEY_Rating) { + m_availableMetaDatas.push_back(QMediaMetaData::UserRating); + } else if (key == PKEY_Keywords) { + m_availableMetaDatas.push_back(QMediaMetaData::Keywords); + } else if (key == PKEY_Language) { + m_availableMetaDatas.push_back(QMediaMetaData::Language); + } else if (key == PKEY_Media_Publisher) { + m_availableMetaDatas.push_back(QMediaMetaData::Publisher); + } else if (key == PKEY_Media_ClassPrimaryID) { + m_availableMetaDatas.push_back(QMediaMetaData::MediaType); + } else if (key == PKEY_Media_Duration) { + m_availableMetaDatas.push_back(QMediaMetaData::Duration); + } else if (key == PKEY_Audio_EncodingBitrate) { + m_availableMetaDatas.push_back(QMediaMetaData::AudioBitRate); + } else if (key == PKEY_Audio_Format) { + m_availableMetaDatas.push_back(QMediaMetaData::AudioCodec); + } else if (key == PKEY_Media_AverageLevel) { + m_availableMetaDatas.push_back(QMediaMetaData::AverageLevel); + } else if (key == PKEY_Audio_ChannelCount) { + m_availableMetaDatas.push_back(QMediaMetaData::ChannelCount); + } else if (key == PKEY_Audio_PeakValue) { + m_availableMetaDatas.push_back(QMediaMetaData::PeakValue); + } else if (key == PKEY_Audio_SampleRate) { + m_availableMetaDatas.push_back(QMediaMetaData::SampleRate); + } else if (key == PKEY_Music_AlbumTitle) { + m_availableMetaDatas.push_back(QMediaMetaData::AlbumTitle); + } else if (key == PKEY_Music_AlbumArtist) { + m_availableMetaDatas.push_back(QMediaMetaData::AlbumArtist); + } else if (key == PKEY_Music_Artist) { + m_availableMetaDatas.push_back(QMediaMetaData::ContributingArtist); + } else if (key == PKEY_Music_Composer) { + m_availableMetaDatas.push_back(QMediaMetaData::Composer); + } else if (key == PKEY_Music_Conductor) { + m_availableMetaDatas.push_back(QMediaMetaData::Conductor); + } else if (key == PKEY_Music_Lyrics) { + m_availableMetaDatas.push_back(QMediaMetaData::Lyrics); + } else if (key == PKEY_Music_Mood) { + m_availableMetaDatas.push_back(QMediaMetaData::Mood); + } else if (key == PKEY_Music_TrackNumber) { + m_availableMetaDatas.push_back(QMediaMetaData::TrackNumber); + } else if (key == PKEY_Music_Genre) { + m_availableMetaDatas.push_back(QMediaMetaData::Genre); + } else if (key == PKEY_ThumbnailStream) { + m_availableMetaDatas.push_back(QMediaMetaData::ThumbnailImage); + } else if (key == PKEY_Video_FrameHeight) { + m_availableMetaDatas.push_back(QMediaMetaData::Resolution); + } else if (key == PKEY_Video_Orientation) { + m_availableMetaDatas.push_back(QMediaMetaData::Orientation); + } else if (key == PKEY_Video_HorizontalAspectRatio) { + m_availableMetaDatas.push_back(QMediaMetaData::PixelAspectRatio); + } else if (key == PKEY_Video_FrameRate) { + m_availableMetaDatas.push_back(QMediaMetaData::VideoFrameRate); + } else if (key == PKEY_Video_EncodingBitrate) { + m_availableMetaDatas.push_back(QMediaMetaData::VideoBitRate); + } else if (key == PKEY_Video_Compression) { + m_availableMetaDatas.push_back(QMediaMetaData::VideoCodec); + } else if (key == PKEY_Video_Director) { + m_availableMetaDatas.push_back(QMediaMetaData::Director); + } else if (key == PKEY_Media_Writer) { + m_availableMetaDatas.push_back(QMediaMetaData::Writer); + } else { + common = false; + //TODO: add more extended keys + } + if (common) + m_commonKeys.push_back(key); + } + } else { + m_content->Release(); + m_content = NULL; + } + } + + if (!m_content) { + //fallback to Vista approach + IMFMetadataProvider *provider = NULL; + if (SUCCEEDED(MFGetService(mediaSource, MF_METADATA_PROVIDER_SERVICE, IID_PPV_ARGS(&provider)))) { + if (SUCCEEDED(provider->GetMFMetadata(sourcePD, 0, 0, &m_metaData))) { + PROPVARIANT varNames; + PropVariantInit(&varNames); + if (SUCCEEDED(m_metaData->GetAllPropertyNames(&varNames)) && varNames.vt == (VT_VECTOR | VT_LPWSTR)) { + ULONG cElements = varNames.calpwstr.cElems; + for (ULONG i = 0; i < cElements; i++) + { + const WCHAR* sName = varNames.calpwstr.pElems[i]; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "metadata: " << QString::fromUtf16(sName); +#endif + if (wcscmp(sName, L"Author") == 0) { + m_availableMetaDatas.push_back(QMediaMetaData::Author); + } else if (wcscmp(sName, L"Title") == 0) { + m_availableMetaDatas.push_back(QMediaMetaData::Title); + } else if (wcscmp(sName, L"Rating") == 0) { + m_availableMetaDatas.push_back(QMediaMetaData::ParentalRating); + } else if (wcscmp(sName, L"Description") == 0) { + m_availableMetaDatas.push_back(QMediaMetaData::Description); + } else if (wcscmp(sName, L"Copyright") == 0) { + m_availableMetaDatas.push_back(QMediaMetaData::Copyright); + //TODO: add more common keys + } else { + m_availableMetaDatas.push_back(QString::fromUtf16(reinterpret_cast<const ushort*>(sName))); + } + m_commonNames.push_back(QString::fromUtf16(reinterpret_cast<const ushort*>(sName))); + } + } + PropVariantClear(&varNames); + } else { + qWarning("Failed to get IMFMetadata"); + } + provider->Release(); + } else { + qWarning("Failed to get IMFMetadataProvider from source"); + } + } + + emit metaDataChanged(); + emit metaDataAvailableChanged(m_metaData || m_content); +} diff --git a/src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h b/src/multimedia/platform/wmf/player/mfmetadatacontrol_p.h new file mode 100644 index 000000000..dcce5bb1d --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfmetadatacontrol_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 Mobility Components. +** +** $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 MFMETADATACONTROL_H +#define MFMETADATACONTROL_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.h> +#include "Mfidl.h" + +QT_USE_NAMESPACE + +class MFMetaDataControl : public QMetaDataReaderControl +{ + Q_OBJECT +public: + MFMetaDataControl(QObject *parent = 0); + ~MFMetaDataControl(); + + bool isMetaDataAvailable() const; + + QVariant metaData(const QString &key) const; + QStringList availableMetaData() const; + + void updateSource(IMFPresentationDescriptor* sourcePD, IMFMediaSource* mediaSource); + +private: + QVariant convertValue(const PROPVARIANT& var) const; + IPropertyStore *m_content; //for Windows7 + IMFMetadata *m_metaData; //for Vista + + QStringList m_availableMetaDatas; + QList<PROPERTYKEY> m_commonKeys; //for Windows7 + QStringList m_commonNames; //for Vista +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfplayercontrol.cpp b/src/multimedia/platform/wmf/player/mfplayercontrol.cpp new file mode 100644 index 000000000..3bb963417 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayercontrol.cpp @@ -0,0 +1,316 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "mfplayercontrol_p.h" +#include <qtcore/qdebug.h> + +//#define DEBUG_MEDIAFOUNDATION + +MFPlayerControl::MFPlayerControl(MFPlayerSession *session) +: QMediaPlayerControl(session) +, m_state(QMediaPlayer::StoppedState) +, m_stateDirty(false) +, m_videoAvailable(false) +, m_audioAvailable(false) +, m_duration(-1) +, m_seekable(false) +, m_session(session) +{ + QObject::connect(m_session, SIGNAL(statusChanged()), this, SLOT(handleStatusChanged())); + QObject::connect(m_session, SIGNAL(videoAvailable()), this, SLOT(handleVideoAvailable())); + QObject::connect(m_session, SIGNAL(audioAvailable()), this, SLOT(handleAudioAvailable())); + QObject::connect(m_session, SIGNAL(durationUpdate(qint64)), this, SLOT(handleDurationUpdate(qint64))); + QObject::connect(m_session, SIGNAL(seekableUpdate(bool)), this, SLOT(handleSeekableUpdate(bool))); + QObject::connect(m_session, SIGNAL(error(QMediaPlayer::Error,QString,bool)), this, SLOT(handleError(QMediaPlayer::Error,QString,bool))); + QObject::connect(m_session, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64))); + QObject::connect(m_session, SIGNAL(volumeChanged(int)), this, SIGNAL(volumeChanged(int))); + QObject::connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool))); + QObject::connect(m_session, SIGNAL(playbackRateChanged(qreal)), this, SIGNAL(playbackRateChanged(qreal))); + QObject::connect(m_session, SIGNAL(bufferStatusChanged(int)), this, SIGNAL(bufferStatusChanged(int))); +} + +MFPlayerControl::~MFPlayerControl() +{ +} + +void MFPlayerControl::setMedia(const QUrl &media, QIODevice *stream) +{ + if (m_state != QMediaPlayer::StoppedState) { + changeState(QMediaPlayer::StoppedState); + m_session->stop(true); + refreshState(); + } + + m_media = media; + m_stream = stream; + resetAudioVideoAvailable(); + handleDurationUpdate(-1); + handleSeekableUpdate(false); + m_session->load(media, stream); + emit mediaChanged(m_media); +} + +void MFPlayerControl::play() +{ + if (m_state == QMediaPlayer::PlayingState) + return; + if (QMediaPlayer::InvalidMedia == m_session->status()) + m_session->load(m_media, m_stream); + + switch (m_session->status()) { + case QMediaPlayer::UnknownMediaStatus: + case QMediaPlayer::NoMedia: + case QMediaPlayer::InvalidMedia: + return; + case QMediaPlayer::LoadedMedia: + case QMediaPlayer::BufferingMedia: + case QMediaPlayer::BufferedMedia: + case QMediaPlayer::EndOfMedia: + changeState(QMediaPlayer::PlayingState); + m_session->start(); + break; + default: //Loading/Stalled + changeState(QMediaPlayer::PlayingState); + break; + } + refreshState(); +} + +void MFPlayerControl::pause() +{ + if (m_state != QMediaPlayer::PlayingState) + return; + changeState(QMediaPlayer::PausedState); + m_session->pause(); + refreshState(); +} + +void MFPlayerControl::stop() +{ + if (m_state == QMediaPlayer::StoppedState) + return; + changeState(QMediaPlayer::StoppedState); + m_session->stop(); + refreshState(); +} + +void MFPlayerControl::changeState(QMediaPlayer::State state) +{ + if (m_state == state) + return; + m_state = state; + m_stateDirty = true; +} + +void MFPlayerControl::refreshState() +{ + if (!m_stateDirty) + return; + m_stateDirty = false; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerControl::emit stateChanged" << m_state; +#endif + emit stateChanged(m_state); +} + +void MFPlayerControl::handleStatusChanged() +{ + QMediaPlayer::MediaStatus status = m_session->status(); + switch (status) { + case QMediaPlayer::EndOfMedia: + changeState(QMediaPlayer::StoppedState); + break; + case QMediaPlayer::InvalidMedia: + break; + case QMediaPlayer::LoadedMedia: + case QMediaPlayer::BufferingMedia: + case QMediaPlayer::BufferedMedia: + if (m_state == QMediaPlayer::PlayingState) + m_session->start(); + break; + } + emit mediaStatusChanged(m_session->status()); + refreshState(); +} + +void MFPlayerControl::handleVideoAvailable() +{ + if (m_videoAvailable) + return; + m_videoAvailable = true; + emit videoAvailableChanged(m_videoAvailable); +} + +void MFPlayerControl::handleAudioAvailable() +{ + if (m_audioAvailable) + return; + m_audioAvailable = true; + emit audioAvailableChanged(m_audioAvailable); +} + +void MFPlayerControl::resetAudioVideoAvailable() +{ + bool videoDirty = false; + if (m_videoAvailable) { + m_videoAvailable = false; + videoDirty = true; + } + if (m_audioAvailable) { + m_audioAvailable = false; + emit audioAvailableChanged(m_audioAvailable); + } + if (videoDirty) + emit videoAvailableChanged(m_videoAvailable); +} + +void MFPlayerControl::handleDurationUpdate(qint64 duration) +{ + if (m_duration == duration) + return; + m_duration = duration; + emit durationChanged(m_duration); +} + +void MFPlayerControl::handleSeekableUpdate(bool seekable) +{ + if (m_seekable == seekable) + return; + m_seekable = seekable; + emit seekableChanged(m_seekable); +} + +QMediaPlayer::State MFPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus MFPlayerControl::mediaStatus() const +{ + return m_session->status(); +} + +qint64 MFPlayerControl::duration() const +{ + return m_duration; +} + +qint64 MFPlayerControl::position() const +{ + return m_session->position(); +} + +void MFPlayerControl::setPosition(qint64 position) +{ + if (!m_seekable || position == m_session->position()) + return; + m_session->setPosition(position); +} + +int MFPlayerControl::volume() const +{ + return m_session->volume(); +} + +void MFPlayerControl::setVolume(int volume) +{ + m_session->setVolume(volume); +} + +bool MFPlayerControl::isMuted() const +{ + return m_session->isMuted(); +} + +void MFPlayerControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +int MFPlayerControl::bufferStatus() const +{ + return m_session->bufferStatus(); +} + +bool MFPlayerControl::isAudioAvailable() const +{ + return m_audioAvailable; +} + +bool MFPlayerControl::isVideoAvailable() const +{ + return m_videoAvailable; +} + +bool MFPlayerControl::isSeekable() const +{ + return m_seekable; +} + +QMediaTimeRange MFPlayerControl::availablePlaybackRanges() const +{ + return m_session->availablePlaybackRanges(); +} + +qreal MFPlayerControl::playbackRate() const +{ + return m_session->playbackRate(); +} + +void MFPlayerControl::setPlaybackRate(qreal rate) +{ + m_session->setPlaybackRate(rate); +} + +QUrl MFPlayerControl::media() const +{ + return m_media; +} + +const QIODevice* MFPlayerControl::mediaStream() const +{ + return m_stream; +} + +void MFPlayerControl::handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal) +{ + if (isFatal) + stop(); + emit error(int(errorCode), errorString); +} diff --git a/src/multimedia/platform/wmf/player/mfplayercontrol_p.h b/src/multimedia/platform/wmf/player/mfplayercontrol_p.h new file mode 100644 index 000000000..35695e0db --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayercontrol_p.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFPLAYERCONTROL_H +#define MFPLAYERCONTROL_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 "QUrl.h" +#include "qmediaplayercontrol.h" + +#include <QtCore/qcoreevent.h> + +#include "mfplayersession_p.h" + +QT_USE_NAMESPACE + +class MFPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT +public: + MFPlayerControl(MFPlayerSession *session); + ~MFPlayerControl(); + + QMediaPlayer::State state() const; + + QMediaPlayer::MediaStatus mediaStatus() const; + + qint64 duration() const; + + qint64 position() const; + void setPosition(qint64 position); + + int volume() const; + void setVolume(int volume); + + bool isMuted() const; + void setMuted(bool muted); + + int bufferStatus() const; + + bool isAudioAvailable() const; + bool isVideoAvailable() const; + + bool isSeekable() const; + + QMediaTimeRange availablePlaybackRanges() const; + + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + + QUrl media() const; + const QIODevice *mediaStream() const; + void setMedia(const QUrl &media, QIODevice *stream); + + void play(); + void pause(); + void stop(); + + bool streamPlaybackSupported() const { return true; } + + +private Q_SLOTS: + void handleStatusChanged(); + void handleVideoAvailable(); + void handleAudioAvailable(); + void handleDurationUpdate(qint64 duration); + void handleSeekableUpdate(bool seekable); + void handleError(QMediaPlayer::Error errorCode, const QString& errorString, bool isFatal); + +private: + void changeState(QMediaPlayer::State state); + void resetAudioVideoAvailable(); + void refreshState(); + + QMediaPlayer::State m_state; + bool m_stateDirty; + QMediaPlayer::MediaStatus m_status; + QMediaPlayer::Error m_error; + + bool m_videoAvailable; + bool m_audioAvailable; + qint64 m_duration; + bool m_seekable; + + QIODevice *m_stream; + QUrl m_media; + MFPlayerSession *m_session; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfplayerservice.cpp b/src/multimedia/platform/wmf/player/mfplayerservice.cpp new file mode 100644 index 000000000..1fce4f6c3 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayerservice.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "QUrl.h" + +#include <QtCore/qdebug.h> + +#include "mfplayercontrol_p.h" +#include "mfevrvideowindowcontrol_p.h" +#include "mfvideorenderercontrol_p.h" +#include "mfaudioendpointcontrol_p.h" +#include "mfaudioprobecontrol_p.h" +#include "mfvideoprobecontrol_p.h" +#include "mfplayerservice_p.h" +#include "mfplayersession_p.h" +#include "mfmetadatacontrol_p.h" + +MFPlayerService::MFPlayerService(QObject *parent) + : QMediaService(parent) + , m_session(0) + , m_videoWindowControl(0) + , m_videoRendererControl(0) +{ + m_audioEndpointControl = new MFAudioEndpointControl(this); + m_session = new MFPlayerSession(this); + m_player = new MFPlayerControl(m_session); + m_metaDataControl = new MFMetaDataControl(this); +} + +MFPlayerService::~MFPlayerService() +{ + m_session->close(); + + if (m_videoWindowControl) + delete m_videoWindowControl; + + if (m_videoRendererControl) + delete m_videoRendererControl; + + m_session->Release(); +} + +QObject *MFPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name, QMediaPlayerControl_iid) == 0) { + return m_player; + } else if (qstrcmp(name, QAudioOutputSelectorControl_iid) == 0) { + return m_audioEndpointControl; + } else if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) { + return m_metaDataControl; + } else if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (!m_videoRendererControl && !m_videoWindowControl) { + m_videoRendererControl = new MFVideoRendererControl; + return m_videoRendererControl; + } + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + if (!m_videoRendererControl && !m_videoWindowControl) { + m_videoWindowControl = new MFEvrVideoWindowControl; + return m_videoWindowControl; + } + } else if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) { + if (m_session) { + MFAudioProbeControl *probe = new MFAudioProbeControl(this); + m_session->addProbe(probe); + return probe; + } + return 0; + } else if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) { + if (m_session) { + MFVideoProbeControl *probe = new MFVideoProbeControl(this); + m_session->addProbe(probe); + return probe; + } + return 0; + } + + return 0; +} + +void MFPlayerService::releaseControl(QObject *control) +{ + if (!control) { + qWarning("QMediaService::releaseControl():" + " Attempted release of null control"); + } else if (control == m_videoRendererControl) { + m_videoRendererControl->setSurface(0); + delete m_videoRendererControl; + m_videoRendererControl = 0; + return; + } else if (control == m_videoWindowControl) { + delete m_videoWindowControl; + m_videoWindowControl = 0; + return; + } + + MFAudioProbeControl* audioProbe = qobject_cast<MFAudioProbeControl*>(control); + if (audioProbe) { + if (m_session) + m_session->removeProbe(audioProbe); + delete audioProbe; + return; + } + + MFVideoProbeControl* videoProbe = qobject_cast<MFVideoProbeControl*>(control); + if (videoProbe) { + if (m_session) + m_session->removeProbe(videoProbe); + delete videoProbe; + return; + } +} + +MFAudioEndpointControl* MFPlayerService::audioEndpointControl() const +{ + return m_audioEndpointControl; +} + +MFVideoRendererControl* MFPlayerService::videoRendererControl() const +{ + return m_videoRendererControl; +} + +MFEvrVideoWindowControl* MFPlayerService::videoWindowControl() const +{ + return m_videoWindowControl; +} + +MFMetaDataControl* MFPlayerService::metaDataControl() const +{ + return m_metaDataControl; +} diff --git a/src/multimedia/platform/wmf/player/mfplayerservice_p.h b/src/multimedia/platform/wmf/player/mfplayerservice_p.h new file mode 100644 index 000000000..50362c381 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayerservice_p.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFPLAYERSERVICE_H +#define MFPLAYERSERVICE_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 <mfapi.h> +#include <mfidl.h> + +#include "qmediaplayer.h" +#include "qmediaservice.h" +#include "qmediatimerange.h" + +QT_BEGIN_NAMESPACE +class QUrl; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class MFEvrVideoWindowControl; +class MFAudioEndpointControl; +class MFVideoRendererControl; +class MFPlayerControl; +class MFMetaDataControl; +class MFPlayerSession; + +class MFPlayerService : public QMediaService +{ + Q_OBJECT +public: + MFPlayerService(QObject *parent = 0); + ~MFPlayerService(); + + QObject *requestControl(const char *name); + void releaseControl(QObject *control); + + MFAudioEndpointControl* audioEndpointControl() const; + MFVideoRendererControl* videoRendererControl() const; + MFEvrVideoWindowControl* videoWindowControl() const; + MFMetaDataControl* metaDataControl() const; + +private: + MFPlayerSession *m_session; + MFVideoRendererControl *m_videoRendererControl; + MFAudioEndpointControl *m_audioEndpointControl; + MFEvrVideoWindowControl *m_videoWindowControl; + MFPlayerControl *m_player; + MFMetaDataControl *m_metaDataControl; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfplayersession.cpp b/src/multimedia/platform/wmf/player/mfplayersession.cpp new file mode 100644 index 000000000..0d1213c84 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayersession.cpp @@ -0,0 +1,1816 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "qmediaplayercontrol.h" + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdatetime.h> +#include <QtCore/qthread.h> +#include <QtCore/qvarlengtharray.h> +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qbuffer.h> + +#include "mfplayercontrol_p.h" +#include "mfevrvideowindowcontrol_p.h" +#include "mfvideorenderercontrol_p.h" +#include "mfaudioendpointcontrol_p.h" + +#include "mfplayersession_p.h" +#include "mfplayerservice_p.h" +#include "mfmetadatacontrol_p.h" +#include <mferror.h> +#include <nserror.h> +#include "private/sourceresolver_p.h" +#include "samplegrabber_p.h" +#include "mftvideo_p.h" +#include <wmcodecdsp.h> + +//#define DEBUG_MEDIAFOUNDATION + +MFPlayerSession::MFPlayerSession(MFPlayerService *playerService) + : m_playerService(playerService) + , m_cRef(1) + , m_session(0) + , m_presentationClock(0) + , m_rateControl(0) + , m_rateSupport(0) + , m_volumeControl(0) + , m_netsourceStatistics(0) + , m_duration(0) + , m_sourceResolver(0) + , m_hCloseEvent(0) + , m_closing(false) + , m_pendingRate(1) + , m_volume(100) + , m_muted(false) + , m_status(QMediaPlayer::NoMedia) + , m_scrubbing(false) + , m_restoreRate(1) + , m_mediaTypes(0) + , m_audioSampleGrabber(0) + , m_audioSampleGrabberNode(0) + , m_videoProbeMFT(0) +{ + QObject::connect(this, SIGNAL(sessionEvent(IMFMediaEvent*)), this, SLOT(handleSessionEvent(IMFMediaEvent*))); + + m_pendingState = NoPending; + ZeroMemory(&m_state, sizeof(m_state)); + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_state.rate = 1.0f; + ZeroMemory(&m_request, sizeof(m_request)); + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + m_request.rate = 1.0f; + + m_audioSampleGrabber = new AudioSampleGrabberCallback; +} + +void MFPlayerSession::close() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "close"; +#endif + + clear(); + if (!m_session) + return; + + HRESULT hr = S_OK; + if (m_session) { + m_closing = true; + hr = m_session->Close(); + if (SUCCEEDED(hr)) { + DWORD dwWaitResult = WaitForSingleObject(m_hCloseEvent, 100); + if (dwWaitResult == WAIT_TIMEOUT) { + qWarning() << "session close time out!"; + } + } + m_closing = false; + } + + if (SUCCEEDED(hr)) { + if (m_session) + m_session->Shutdown(); + if (m_sourceResolver) + m_sourceResolver->shutdown(); + } + if (m_sourceResolver) { + m_sourceResolver->Release(); + m_sourceResolver = 0; + } + if (m_videoProbeMFT) { + m_videoProbeMFT->Release(); + m_videoProbeMFT = 0; + } + + if (m_playerService->videoRendererControl()) { + m_playerService->videoRendererControl()->releaseActivate(); + } else if (m_playerService->videoWindowControl()) { + m_playerService->videoWindowControl()->releaseActivate(); + } + + if (m_session) + m_session->Release(); + m_session = 0; + if (m_hCloseEvent) + CloseHandle(m_hCloseEvent); + m_hCloseEvent = 0; +} + +void MFPlayerSession::addProbe(MFAudioProbeControl *probe) +{ + m_audioSampleGrabber->addProbe(probe); +} + +void MFPlayerSession::removeProbe(MFAudioProbeControl *probe) +{ + m_audioSampleGrabber->removeProbe(probe); +} + +void MFPlayerSession::addProbe(MFVideoProbeControl* probe) +{ + if (m_videoProbes.contains(probe)) + return; + + m_videoProbes.append(probe); + + if (m_videoProbeMFT) + m_videoProbeMFT->addProbe(probe); +} + +void MFPlayerSession::removeProbe(MFVideoProbeControl* probe) +{ + m_videoProbes.removeOne(probe); + + if (m_videoProbeMFT) + m_videoProbeMFT->removeProbe(probe); +} + +MFPlayerSession::~MFPlayerSession() +{ + m_audioSampleGrabber->Release(); +} + + +void MFPlayerSession::load(const QUrl &url, QIODevice *stream) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "load"; +#endif + clear(); + + if (m_status == QMediaPlayer::LoadingMedia && m_sourceResolver) + m_sourceResolver->cancel(); + + if (url.isEmpty() && !stream) { + changeStatus(QMediaPlayer::NoMedia); + } else if (stream && (!stream->isReadable())) { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Invalid stream source."), true); + } else { + createSession(); + changeStatus(QMediaPlayer::LoadingMedia); + m_sourceResolver->load(url, stream); + } + emit positionChanged(position()); +} + +void MFPlayerSession::handleSourceError(long hr) +{ + QString errorString; + QMediaPlayer::Error errorCode = QMediaPlayer::ResourceError; + switch (hr) { + case QMediaPlayer::FormatError: + errorCode = QMediaPlayer::FormatError; + errorString = tr("Attempting to play invalid Qt resource."); + break; + case NS_E_FILE_NOT_FOUND: + errorString = tr("The system cannot find the file specified."); + break; + case NS_E_SERVER_NOT_FOUND: + errorString = tr("The specified server could not be found."); + break; + case MF_E_UNSUPPORTED_BYTESTREAM_TYPE: + errorCode = QMediaPlayer::FormatError; + errorString = tr("Unsupported media type."); + break; + default: + errorString = tr("Failed to load source."); + break; + } + changeStatus(QMediaPlayer::InvalidMedia); + emit error(errorCode, errorString, true); +} + +void MFPlayerSession::handleMediaSourceReady() +{ + if (QMediaPlayer::LoadingMedia != m_status || !m_sourceResolver || m_sourceResolver != sender()) + return; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "handleMediaSourceReady"; +#endif + HRESULT hr = S_OK; + IMFMediaSource* mediaSource = m_sourceResolver->mediaSource(); + + DWORD dwCharacteristics = 0; + mediaSource->GetCharacteristics(&dwCharacteristics); + emit seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics); + + IMFPresentationDescriptor* sourcePD; + hr = mediaSource->CreatePresentationDescriptor(&sourcePD); + if (SUCCEEDED(hr)) { + m_duration = 0; + m_playerService->metaDataControl()->updateSource(sourcePD, mediaSource); + sourcePD->GetUINT64(MF_PD_DURATION, &m_duration); + //convert from 100 nanosecond to milisecond + emit durationUpdate(qint64(m_duration / 10000)); + setupPlaybackTopology(mediaSource, sourcePD); + sourcePD->Release(); + } else { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Cannot create presentation descriptor."), true); + } +} + +MFPlayerSession::MediaType MFPlayerSession::getStreamType(IMFStreamDescriptor *stream) const +{ + if (!stream) + return Unknown; + + struct SafeRelease { + IMFMediaTypeHandler *ptr = nullptr; + ~SafeRelease() { if (ptr) ptr->Release(); } + } typeHandler; + if (SUCCEEDED(stream->GetMediaTypeHandler(&typeHandler.ptr))) { + GUID guidMajorType; + if (SUCCEEDED(typeHandler.ptr->GetMajorType(&guidMajorType))) { + if (guidMajorType == MFMediaType_Audio) + return Audio; + else if (guidMajorType == MFMediaType_Video) + return Video; + } + } + + return Unknown; +} + +void MFPlayerSession::setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD) +{ + HRESULT hr = S_OK; + // Get the number of streams in the media source. + DWORD cSourceStreams = 0; + hr = sourcePD->GetStreamDescriptorCount(&cSourceStreams); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::UnknownMediaStatus); + emit error(QMediaPlayer::ResourceError, tr("Failed to get stream count."), true); + return; + } + + IMFTopology *topology; + hr = MFCreateTopology(&topology); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::UnknownMediaStatus); + emit error(QMediaPlayer::ResourceError, tr("Failed to create topology."), true); + return; + } + + // Remember output node id for a first video stream + TOPOID outputNodeId = -1; + + // For each stream, create the topology nodes and add them to the topology. + DWORD succeededCount = 0; + for (DWORD i = 0; i < cSourceStreams; i++) + { + BOOL fSelected = FALSE; + bool streamAdded = false; + IMFStreamDescriptor *streamDesc = NULL; + + HRESULT hr = sourcePD->GetStreamDescriptorByIndex(i, &fSelected, &streamDesc); + if (SUCCEEDED(hr)) { + // The media might have multiple audio and video streams, + // only use one of each kind, and only if it is selected by default. + MediaType mediaType = getStreamType(streamDesc); + if (mediaType != Unknown + && ((m_mediaTypes & mediaType) == 0) // Check if this type isn't already added + && fSelected) { + + IMFTopologyNode *sourceNode = addSourceNode(topology, source, sourcePD, streamDesc); + if (sourceNode) { + IMFTopologyNode *outputNode = addOutputNode(mediaType, topology, 0); + if (outputNode) { + bool connected = false; + if (mediaType == Audio) { + if (!m_audioSampleGrabberNode) + connected = setupAudioSampleGrabber(topology, sourceNode, outputNode); + } else if (mediaType == Video && outputNodeId == -1) { + // Remember video output node ID. + outputNode->GetTopoNodeID(&outputNodeId); + } + + if (!connected) + hr = sourceNode->ConnectOutput(0, outputNode, 0); + + if (FAILED(hr)) { + emit error(QMediaPlayer::FormatError, tr("Unable to play any stream."), false); + } else { + streamAdded = true; + succeededCount++; + m_mediaTypes |= mediaType; + switch (mediaType) { + case Audio: + emit audioAvailable(); + break; + case Video: + emit videoAvailable(); + break; + } + } + outputNode->Release(); + } + sourceNode->Release(); + } + } + + if (fSelected && !streamAdded) + sourcePD->DeselectStream(i); + + streamDesc->Release(); + } + } + + if (succeededCount == 0) { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::ResourceError, tr("Unable to play."), true); + } else { + if (outputNodeId != -1) { + topology = insertMFT(topology, outputNodeId); + } + + hr = m_session->SetTopology(MFSESSION_SETTOPOLOGY_IMMEDIATE, topology); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::UnknownMediaStatus); + emit error(QMediaPlayer::ResourceError, tr("Failed to set topology."), true); + } + } + topology->Release(); +} + +IMFTopologyNode* MFPlayerSession::addSourceNode(IMFTopology* topology, IMFMediaSource* source, + IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc) +{ + IMFTopologyNode *node = NULL; + HRESULT hr = MFCreateTopologyNode(MF_TOPOLOGY_SOURCESTREAM_NODE, &node); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_SOURCE, source); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_PRESENTATION_DESCRIPTOR, presentationDesc); + if (SUCCEEDED(hr)) { + hr = node->SetUnknown(MF_TOPONODE_STREAM_DESCRIPTOR, streamDesc); + if (SUCCEEDED(hr)) { + hr = topology->AddNode(node); + if (SUCCEEDED(hr)) + return node; + } + } + } + node->Release(); + } + return NULL; +} + +IMFTopologyNode* MFPlayerSession::addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID) +{ + IMFTopologyNode *node = NULL; + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &node))) + return NULL; + + IMFActivate *activate = NULL; + if (mediaType == Audio) { + activate = m_playerService->audioEndpointControl()->createActivate(); + } else if (mediaType == Video) { + if (m_playerService->videoRendererControl()) { + activate = m_playerService->videoRendererControl()->createActivate(); + } else if (m_playerService->videoWindowControl()) { + activate = m_playerService->videoWindowControl()->createActivate(); + } else { + qWarning() << "no videoWindowControl or videoRendererControl, unable to add output node for video data"; + } + } else { + // Unknown stream type. + emit error(QMediaPlayer::FormatError, tr("Unknown stream type."), false); + } + + if (!activate + || FAILED(node->SetObject(activate)) + || FAILED(node->SetUINT32(MF_TOPONODE_STREAMID, sinkID)) + || FAILED(node->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE)) + || FAILED(topology->AddNode(node))) { + node->Release(); + node = NULL; + } + + return node; +} + +bool MFPlayerSession::addAudioSampleGrabberNode(IMFTopology *topology) +{ + HRESULT hr = S_OK; + IMFMediaType *pType = 0; + IMFActivate *sinkActivate = 0; + do { + hr = MFCreateMediaType(&pType); + if (FAILED(hr)) + break; + + hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + if (FAILED(hr)) + break; + + hr = pType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + if (FAILED(hr)) + break; + + hr = MFCreateSampleGrabberSinkActivate(pType, m_audioSampleGrabber, &sinkActivate); + if (FAILED(hr)) + break; + + // Note: Data is distorted if this attribute is enabled + hr = sinkActivate->SetUINT32(MF_SAMPLEGRABBERSINK_IGNORE_CLOCK, FALSE); + if (FAILED(hr)) + break; + + hr = MFCreateTopologyNode(MF_TOPOLOGY_OUTPUT_NODE, &m_audioSampleGrabberNode); + if (FAILED(hr)) + break; + + hr = m_audioSampleGrabberNode->SetObject(sinkActivate); + if (FAILED(hr)) + break; + + hr = m_audioSampleGrabberNode->SetUINT32(MF_TOPONODE_STREAMID, 0); // Identifier of the stream sink. + if (FAILED(hr)) + break; + + hr = m_audioSampleGrabberNode->SetUINT32(MF_TOPONODE_NOSHUTDOWN_ON_REMOVE, FALSE); + if (FAILED(hr)) + break; + + hr = topology->AddNode(m_audioSampleGrabberNode); + if (FAILED(hr)) + break; + + pType->Release(); + sinkActivate->Release(); + return true; + } while (false); + + if (pType) + pType->Release(); + if (sinkActivate) + sinkActivate->Release(); + if (m_audioSampleGrabberNode) { + m_audioSampleGrabberNode->Release(); + m_audioSampleGrabberNode = NULL; + } + return false; +} + +bool MFPlayerSession::setupAudioSampleGrabber(IMFTopology *topology, IMFTopologyNode *sourceNode, IMFTopologyNode *outputNode) +{ + if (!addAudioSampleGrabberNode(topology)) + return false; + + HRESULT hr = S_OK; + IMFTopologyNode *pTeeNode = NULL; + + IMFMediaTypeHandler *typeHandler = NULL; + IMFMediaType *mediaType = NULL; + do { + hr = MFCreateTopologyNode(MF_TOPOLOGY_TEE_NODE, &pTeeNode); + if (FAILED(hr)) + break; + hr = sourceNode->ConnectOutput(0, pTeeNode, 0); + if (FAILED(hr)) + break; + hr = pTeeNode->ConnectOutput(0, outputNode, 0); + if (FAILED(hr)) + break; + hr = pTeeNode->ConnectOutput(1, m_audioSampleGrabberNode, 0); + if (FAILED(hr)) + break; + } while (false); + + if (pTeeNode) + pTeeNode->Release(); + if (mediaType) + mediaType->Release(); + if (typeHandler) + typeHandler->Release(); + return hr == S_OK; +} + +QAudioFormat MFPlayerSession::audioFormatForMFMediaType(IMFMediaType *mediaType) const +{ + WAVEFORMATEX *wfx = 0; + UINT32 size; + HRESULT hr = MFCreateWaveFormatExFromMFMediaType(mediaType, &wfx, &size, MFWaveFormatExConvertFlag_Normal); + if (FAILED(hr)) + return QAudioFormat(); + + if (size < sizeof(WAVEFORMATEX)) { + CoTaskMemFree(wfx); + return QAudioFormat(); + } + + if (wfx->wFormatTag != WAVE_FORMAT_PCM) { + CoTaskMemFree(wfx); + return QAudioFormat(); + } + + QAudioFormat format; + format.setSampleRate(wfx->nSamplesPerSec); + format.setChannelCount(wfx->nChannels); + format.setSampleSize(wfx->wBitsPerSample); + format.setCodec("audio/x-raw"); + format.setByteOrder(QAudioFormat::LittleEndian); + if (format.sampleSize() == 8) + format.setSampleType(QAudioFormat::UnSignedInt); + else + format.setSampleType(QAudioFormat::SignedInt); + + CoTaskMemFree(wfx); + return format; +} + +// BindOutputNode +// Sets the IMFStreamSink pointer on an output node. +// IMFActivate pointer in the output node must be converted to an +// IMFStreamSink pointer before the topology loader resolves the topology. +HRESULT BindOutputNode(IMFTopologyNode *pNode) +{ + IUnknown *nodeObject = NULL; + IMFActivate *activate = NULL; + IMFStreamSink *stream = NULL; + IMFMediaSink *sink = NULL; + + HRESULT hr = pNode->GetObject(&nodeObject); + if (FAILED(hr)) + return hr; + + hr = nodeObject->QueryInterface(IID_PPV_ARGS(&activate)); + if (SUCCEEDED(hr)) { + DWORD dwStreamID = 0; + + // Try to create the media sink. + hr = activate->ActivateObject(IID_PPV_ARGS(&sink)); + if (SUCCEEDED(hr)) + dwStreamID = MFGetAttributeUINT32(pNode, MF_TOPONODE_STREAMID, 0); + + if (SUCCEEDED(hr)) { + // First check if the media sink already has a stream sink with the requested ID. + hr = sink->GetStreamSinkById(dwStreamID, &stream); + if (FAILED(hr)) { + // Create the stream sink. + hr = sink->AddStreamSink(dwStreamID, NULL, &stream); + } + } + + // Replace the node's object pointer with the stream sink. + if (SUCCEEDED(hr)) { + hr = pNode->SetObject(stream); + } + } else { + hr = nodeObject->QueryInterface(IID_PPV_ARGS(&stream)); + } + + if (nodeObject) + nodeObject->Release(); + if (activate) + activate->Release(); + if (stream) + stream->Release(); + if (sink) + sink->Release(); + return hr; +} + +// BindOutputNodes +// Sets the IMFStreamSink pointers on all of the output nodes in a topology. +HRESULT BindOutputNodes(IMFTopology *pTopology) +{ + IMFCollection *collection; + + // Get the collection of output nodes. + HRESULT hr = pTopology->GetOutputNodeCollection(&collection); + + // Enumerate all of the nodes in the collection. + if (SUCCEEDED(hr)) { + DWORD cNodes; + hr = collection->GetElementCount(&cNodes); + + if (SUCCEEDED(hr)) { + for (DWORD i = 0; i < cNodes; i++) { + IUnknown *element; + hr = collection->GetElement(i, &element); + if (FAILED(hr)) + break; + + IMFTopologyNode *node; + hr = element->QueryInterface(IID_IMFTopologyNode, (void**)&node); + element->Release(); + if (FAILED(hr)) + break; + + // Bind this node. + hr = BindOutputNode(node); + node->Release(); + if (FAILED(hr)) + break; + } + } + collection->Release(); + } + + return hr; +} + +// This method binds output nodes to complete the topology, +// then loads the topology and inserts MFT between the output node +// and a filter connected to the output node. +IMFTopology *MFPlayerSession::insertMFT(IMFTopology *topology, TOPOID outputNodeId) +{ + bool isNewTopology = false; + + IMFTopoLoader *topoLoader = 0; + IMFTopology *resolvedTopology = 0; + IMFCollection *outputNodes = 0; + + do { + if (FAILED(BindOutputNodes(topology))) + break; + + if (FAILED(MFCreateTopoLoader(&topoLoader))) + break; + + if (FAILED(topoLoader->Load(topology, &resolvedTopology, NULL))) { + // Topology could not be resolved, adding ourselves a color converter + // to the topology might solve the problem + insertColorConverter(topology, outputNodeId); + if (FAILED(topoLoader->Load(topology, &resolvedTopology, NULL))) + break; + } + + if (insertResizer(resolvedTopology)) + isNewTopology = true; + + // Get all output nodes and search for video output node. + if (FAILED(resolvedTopology->GetOutputNodeCollection(&outputNodes))) + break; + + DWORD elementCount = 0; + if (FAILED(outputNodes->GetElementCount(&elementCount))) + break; + + for (DWORD n = 0; n < elementCount; n++) { + IUnknown *element = 0; + IMFTopologyNode *node = 0; + IUnknown *outputObject = 0; + IMFTopologyNode *inputNode = 0; + IMFTopologyNode *mftNode = 0; + bool mftAdded = false; + + do { + if (FAILED(outputNodes->GetElement(n, &element))) + break; + + if (FAILED(element->QueryInterface(IID_IMFTopologyNode, (void**)&node))) + break; + + TOPOID id; + if (FAILED(node->GetTopoNodeID(&id))) + break; + + if (id != outputNodeId) + break; + + if (FAILED(node->GetObject(&outputObject))) + break; + + m_videoProbeMFT->setVideoSink(outputObject); + + // Insert MFT between the output node and the node connected to it. + DWORD outputIndex = 0; + if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) + break; + + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode))) + break; + + if (FAILED(mftNode->SetObject(m_videoProbeMFT))) + break; + + if (FAILED(resolvedTopology->AddNode(mftNode))) + break; + + if (FAILED(inputNode->ConnectOutput(0, mftNode, 0))) + break; + + if (FAILED(mftNode->ConnectOutput(0, node, 0))) + break; + + mftAdded = true; + isNewTopology = true; + } while (false); + + if (mftNode) + mftNode->Release(); + if (inputNode) + inputNode->Release(); + if (node) + node->Release(); + if (element) + element->Release(); + if (outputObject) + outputObject->Release(); + + if (mftAdded) + break; + else + m_videoProbeMFT->setVideoSink(NULL); + } + } while (false); + + if (outputNodes) + outputNodes->Release(); + + if (topoLoader) + topoLoader->Release(); + + if (isNewTopology) { + topology->Release(); + return resolvedTopology; + } + + if (resolvedTopology) + resolvedTopology->Release(); + + return topology; +} + +// This method checks if the topology contains a color converter transform (CColorConvertDMO), +// if it does it inserts a resizer transform (CResizerDMO) to handle dynamic frame size change +// of the video stream. +// Returns true if it inserted a resizer +bool MFPlayerSession::insertResizer(IMFTopology *topology) +{ + bool inserted = false; + WORD elementCount = 0; + IMFTopologyNode *node = 0; + IUnknown *object = 0; + IWMColorConvProps *colorConv = 0; + IMFTransform *resizer = 0; + IMFTopologyNode *resizerNode = 0; + IMFTopologyNode *inputNode = 0; + + HRESULT hr = topology->GetNodeCount(&elementCount); + if (FAILED(hr)) + return false; + + for (WORD i = 0; i < elementCount; ++i) { + if (node) { + node->Release(); + node = 0; + } + if (object) { + object->Release(); + object = 0; + } + + if (FAILED(topology->GetNode(i, &node))) + break; + + MF_TOPOLOGY_TYPE nodeType; + if (FAILED(node->GetNodeType(&nodeType))) + break; + + if (nodeType != MF_TOPOLOGY_TRANSFORM_NODE) + continue; + + if (FAILED(node->GetObject(&object))) + break; + + if (FAILED(object->QueryInterface(&colorConv))) + continue; + + if (FAILED(CoCreateInstance(CLSID_CResizerDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (void**)&resizer))) + break; + + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &resizerNode))) + break; + + if (FAILED(resizerNode->SetObject(resizer))) + break; + + if (FAILED(topology->AddNode(resizerNode))) + break; + + DWORD outputIndex = 0; + if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) { + topology->RemoveNode(resizerNode); + break; + } + + if (FAILED(inputNode->ConnectOutput(0, resizerNode, 0))) { + topology->RemoveNode(resizerNode); + break; + } + + if (FAILED(resizerNode->ConnectOutput(0, node, 0))) { + inputNode->ConnectOutput(0, node, 0); + topology->RemoveNode(resizerNode); + break; + } + + inserted = true; + break; + } + + if (node) + node->Release(); + if (object) + object->Release(); + if (colorConv) + colorConv->Release(); + if (resizer) + resizer->Release(); + if (resizerNode) + resizerNode->Release(); + if (inputNode) + inputNode->Release(); + + return inserted; +} + +// This method inserts a color converter (CColorConvertDMO) in the topology, +// typically to convert to RGB format. +// Usually this converter is automatically inserted when the topology is resolved but +// for some reason it fails to do so in some cases, we then do it ourselves. +void MFPlayerSession::insertColorConverter(IMFTopology *topology, TOPOID outputNodeId) +{ + IMFCollection *outputNodes = 0; + + if (FAILED(topology->GetOutputNodeCollection(&outputNodes))) + return; + + DWORD elementCount = 0; + if (FAILED(outputNodes->GetElementCount(&elementCount))) + goto done; + + for (DWORD n = 0; n < elementCount; n++) { + IUnknown *element = 0; + IMFTopologyNode *node = 0; + IMFTopologyNode *inputNode = 0; + IMFTopologyNode *mftNode = 0; + IMFTransform *converter = 0; + + do { + if (FAILED(outputNodes->GetElement(n, &element))) + break; + + if (FAILED(element->QueryInterface(IID_IMFTopologyNode, (void**)&node))) + break; + + TOPOID id; + if (FAILED(node->GetTopoNodeID(&id))) + break; + + if (id != outputNodeId) + break; + + DWORD outputIndex = 0; + if (FAILED(node->GetInput(0, &inputNode, &outputIndex))) + break; + + if (FAILED(MFCreateTopologyNode(MF_TOPOLOGY_TRANSFORM_NODE, &mftNode))) + break; + + if (FAILED(CoCreateInstance(CLSID_CColorConvertDMO, NULL, CLSCTX_INPROC_SERVER, IID_IMFTransform, (void**)&converter))) + break; + + if (FAILED(mftNode->SetObject(converter))) + break; + + if (FAILED(topology->AddNode(mftNode))) + break; + + if (FAILED(inputNode->ConnectOutput(0, mftNode, 0))) + break; + + if (FAILED(mftNode->ConnectOutput(0, node, 0))) + break; + + } while (false); + + if (mftNode) + mftNode->Release(); + if (inputNode) + inputNode->Release(); + if (node) + node->Release(); + if (element) + element->Release(); + if (converter) + converter->Release(); + } + +done: + if (outputNodes) + outputNodes->Release(); +} + +void MFPlayerSession::stop(bool immediate) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "stop"; +#endif + if (!immediate && m_pendingState != NoPending) { + m_request.setCommand(CmdStop); + } else { + if (m_state.command == CmdStop) + return; + + if (m_scrubbing) + scrub(false); + + if (SUCCEEDED(m_session->Stop())) { + m_state.setCommand(CmdStop); + m_pendingState = CmdPending; + if (m_status != QMediaPlayer::EndOfMedia) { + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = 0; + } + } else { + emit error(QMediaPlayer::ResourceError, tr("Failed to stop."), true); + } + } +} + +void MFPlayerSession::start() +{ + if (m_status == QMediaPlayer::EndOfMedia) + m_varStart.hVal.QuadPart = 0; // restart from the beginning + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "start"; +#endif + + if (m_pendingState != NoPending) { + m_request.setCommand(CmdStart); + } else { + if (m_state.command == CmdStart) + return; + + if (m_scrubbing) + scrub(false); + + if (SUCCEEDED(m_session->Start(&GUID_NULL, &m_varStart))) { + m_state.setCommand(CmdStart); + m_pendingState = CmdPending; + PropVariantInit(&m_varStart); + } else { + emit error(QMediaPlayer::ResourceError, tr("failed to start playback"), true); + } + } +} + +void MFPlayerSession::pause() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "pause"; +#endif + if (m_pendingState != NoPending) { + m_request.setCommand(CmdPause); + } else { + if (m_state.command == CmdPause) + return; + + if (SUCCEEDED(m_session->Pause())) { + m_state.setCommand(CmdPause); + m_pendingState = CmdPending; + } else { + emit error(QMediaPlayer::ResourceError, tr("Failed to pause."), false); + } + } +} + +void MFPlayerSession::changeStatus(QMediaPlayer::MediaStatus newStatus) +{ + if (m_status == newStatus) + return; +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerSession::changeStatus" << newStatus; +#endif + m_status = newStatus; + emit statusChanged(); +} + +QMediaPlayer::MediaStatus MFPlayerSession::status() const +{ + return m_status; +} + +void MFPlayerSession::createSession() +{ + close(); + + m_hCloseEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + m_sourceResolver = new SourceResolver(); + QObject::connect(m_sourceResolver, SIGNAL(mediaSourceReady()), this, SLOT(handleMediaSourceReady())); + QObject::connect(m_sourceResolver, SIGNAL(error(long)), this, SLOT(handleSourceError(long))); + + m_videoProbeMFT = new MFTransform; + for (int i = 0; i < m_videoProbes.size(); ++i) + m_videoProbeMFT->addProbe(m_videoProbes.at(i)); + + Q_ASSERT(m_session == NULL); + HRESULT hr = MFCreateMediaSession(NULL, &m_session); + if (FAILED(hr)) { + changeStatus(QMediaPlayer::UnknownMediaStatus); + emit error(QMediaPlayer::ResourceError, tr("Unable to create mediasession."), true); + } + + hr = m_session->BeginGetEvent(this, m_session); + + if (FAILED(hr)) { + changeStatus(QMediaPlayer::UnknownMediaStatus); + emit error(QMediaPlayer::ResourceError, tr("Unable to pull session events."), false); + } + + PropVariantInit(&m_varStart); + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = 0; +} + +qint64 MFPlayerSession::position() +{ + if (m_request.command == CmdSeek || m_request.command == CmdSeekResume) + return m_request.start; + + if (m_pendingState == SeekPending) + return m_state.start; + + if (m_state.command == CmdStop) + return qint64(m_varStart.hVal.QuadPart / 10000); + + if (m_presentationClock) { + MFTIME time, sysTime; + if (FAILED(m_presentationClock->GetCorrelatedTime(0, &time, &sysTime))) + return 0; + return qint64(time / 10000); + } + return 0; +} + +void MFPlayerSession::setPosition(qint64 position) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPosition"; +#endif + if (m_pendingState != NoPending) { + m_request.setCommand(CmdSeek); + m_request.start = position; + } else { + setPositionInternal(position, CmdNone); + } +} + +void MFPlayerSession::setPositionInternal(qint64 position, Command requestCmd) +{ + if (m_status == QMediaPlayer::EndOfMedia) + changeStatus(QMediaPlayer::LoadedMedia); + if (m_state.command == CmdStop && requestCmd != CmdSeekResume) { + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = LONGLONG(position * 10000); + // Even though the position is not actually set on the session yet, + // report it to have changed anyway for UI controls to be updated + emit positionChanged(this->position()); + return; + } + + if (m_state.command == CmdPause) + scrub(true); + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPositionInternal"; +#endif + + PROPVARIANT varStart; + varStart.vt = VT_I8; + varStart.hVal.QuadPart = LONGLONG(position * 10000); + if (SUCCEEDED(m_session->Start(NULL, &varStart))) + { + PropVariantInit(&m_varStart); + // Store the pending state. + m_state.setCommand(CmdStart); + m_state.start = position; + m_pendingState = SeekPending; + } else { + emit error(QMediaPlayer::ResourceError, tr("Failed to seek."), true); + } +} + +qreal MFPlayerSession::playbackRate() const +{ + if (m_scrubbing) + return m_restoreRate; + return m_state.rate; +} + +void MFPlayerSession::setPlaybackRate(qreal rate) +{ + if (m_scrubbing) { + m_restoreRate = rate; + emit playbackRateChanged(rate); + return; + } + setPlaybackRateInternal(rate); +} + +void MFPlayerSession::setPlaybackRateInternal(qreal rate) +{ + if (rate == m_request.rate) + return; + + m_pendingRate = rate; + if (!m_rateSupport) + return; + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "setPlaybackRate"; +#endif + BOOL isThin = FALSE; + + //from MSDN http://msdn.microsoft.com/en-us/library/aa965220%28v=vs.85%29.aspx + //Thinning applies primarily to video streams. + //In thinned mode, the source drops delta frames and deliver only key frames. + //At very high playback rates, the source might skip some key frames (for example, deliver every other key frame). + + if (FAILED(m_rateSupport->IsRateSupported(FALSE, rate, NULL))) { + isThin = TRUE; + if (FAILED(m_rateSupport->IsRateSupported(isThin, rate, NULL))) { + qWarning() << "unable to set playbackrate = " << rate; + m_pendingRate = m_request.rate = m_state.rate; + return; + } + } + if (m_pendingState != NoPending) { + m_request.rate = rate; + m_request.isThin = isThin; + // Remember the current transport state (play, paused, etc), so that we + // can restore it after the rate change, if necessary. However, if + // anothercommand is already pending, that one takes precedent. + if (m_request.command == CmdNone) + m_request.setCommand(m_state.command); + } else { + //No pending operation. Commit the new rate. + commitRateChange(rate, isThin); + } +} + +void MFPlayerSession::commitRateChange(qreal rate, BOOL isThin) +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "commitRateChange"; +#endif + Q_ASSERT(m_pendingState == NoPending); + MFTIME hnsSystemTime = 0; + MFTIME hnsClockTime = 0; + Command cmdNow = m_state.command; + bool resetPosition = false; + // Allowed rate transitions: + // Positive <-> negative: Stopped + // Negative <-> zero: Stopped + // Postive <-> zero: Paused or stopped + if ((rate > 0 && m_state.rate <= 0) || (rate < 0 && m_state.rate >= 0)) { + if (cmdNow == CmdStart) { + // Get the current clock position. This will be the restart time. + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + Q_ASSERT(hnsSystemTime != 0); + + if (rate < 0 || m_state.rate < 0) + m_request.setCommand(CmdSeekResume); + else if (isThin || m_state.isThin) + m_request.setCommand(CmdStartAndSeek); + else + m_request.setCommand(CmdStart); + + // We need to stop only when dealing with negative rates + if (rate >= 0 && m_state.rate >= 0) + pause(); + else + stop(); + + // If we deal with negative rates, we stopped the session and consequently + // reset the position to zero. We then need to resume to the current position. + m_request.start = hnsClockTime / 10000; + } else if (cmdNow == CmdPause) { + if (rate < 0 || m_state.rate < 0) { + // The current state is paused. + // For this rate change, the session must be stopped. However, the + // session cannot transition back from stopped to paused. + // Therefore, this rate transition is not supported while paused. + qWarning() << "Unable to change rate from positive to negative or vice versa in paused state"; + rate = m_state.rate; + isThin = m_state.isThin; + goto done; + } + + // This happens when resuming playback after scrubbing in pause mode. + // This transition requires the session to be paused. Even though our + // internal state is set to paused, the session might not be so we need + // to enforce it + if (rate > 0 && m_state.rate == 0) { + m_state.setCommand(CmdNone); + pause(); + } + } + } else if (rate == 0 && m_state.rate > 0) { + if (cmdNow != CmdPause) { + // Transition to paused. + // This transisition requires the paused state. + // Pause and set the rate. + pause(); + + // Request: Switch back to current state. + m_request.setCommand(cmdNow); + } + } else if (rate == 0 && m_state.rate < 0) { + // Changing rate from negative to zero requires to stop the session + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + + m_request.setCommand(CmdSeekResume); + + stop(); + + // Resume to the current position (stop() will reset the position to 0) + m_request.start = hnsClockTime / 10000; + } else if (!isThin && m_state.isThin) { + if (cmdNow == CmdStart) { + // When thinning, only key frames are read and presented. Going back + // to normal playback requires to reset the current position to force + // the pipeline to decode the actual frame at the current position + // (which might be earlier than the last decoded key frame) + resetPosition = true; + } else if (cmdNow == CmdPause) { + // If paused, don't reset the position until we resume, otherwise + // a new frame will be rendered + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + m_request.setCommand(CmdSeekResume); + m_request.start = hnsClockTime / 10000; + } + + } + + // Set the rate. + if (FAILED(m_rateControl->SetRate(isThin, rate))) { + qWarning() << "failed to set playbackrate = " << rate; + rate = m_state.rate; + isThin = m_state.isThin; + goto done; + } + + if (resetPosition) { + m_presentationClock->GetCorrelatedTime(0, &hnsClockTime, &hnsSystemTime); + setPosition(hnsClockTime / 10000); + } + +done: + // Adjust our current rate and requested rate. + m_pendingRate = m_request.rate = m_state.rate = rate; + if (rate != 0) + m_state.isThin = isThin; + emit playbackRateChanged(rate); +} + +void MFPlayerSession::scrub(bool enableScrub) +{ + if (m_scrubbing == enableScrub) + return; + + m_scrubbing = enableScrub; + + if (!canScrub()) { + if (!enableScrub) + m_pendingRate = m_restoreRate; + return; + } + + if (enableScrub) { + // Enter scrubbing mode. Cache the rate. + m_restoreRate = m_request.rate; + setPlaybackRateInternal(0.0f); + } else { + // Leaving scrubbing mode. Restore the old rate. + setPlaybackRateInternal(m_restoreRate); + } +} + +int MFPlayerSession::volume() const +{ + return m_volume; +} + +void MFPlayerSession::setVolume(int volume) +{ + if (m_volume == volume) + return; + m_volume = volume; + + if (!m_muted) + setVolumeInternal(volume); + + emit volumeChanged(m_volume); +} + +bool MFPlayerSession::isMuted() const +{ + return m_muted; +} + +void MFPlayerSession::setMuted(bool muted) +{ + if (m_muted == muted) + return; + m_muted = muted; + + setVolumeInternal(muted ? 0 : m_volume); + + emit mutedChanged(m_muted); +} + +void MFPlayerSession::setVolumeInternal(int volume) +{ + if (m_volumeControl) { + quint32 channelCount = 0; + if (!SUCCEEDED(m_volumeControl->GetChannelCount(&channelCount)) + || channelCount == 0) + return; + + float scaled = volume * 0.01f; + for (quint32 i = 0; i < channelCount; ++i) + m_volumeControl->SetChannelVolume(i, scaled); + } +} + +int MFPlayerSession::bufferStatus() +{ + if (!m_netsourceStatistics) + return 0; + PROPVARIANT var; + PropVariantInit(&var); + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_BUFFERPROGRESS_ID; + int progress = -1; + // GetValue returns S_FALSE if the property is not available, which has + // a value > 0. We therefore can't use the SUCCEEDED macro here. + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + progress = var.lVal; + PropVariantClear(&var); + } + +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "bufferStatus: progress = " << progress; +#endif + + return progress; +} + +QMediaTimeRange MFPlayerSession::availablePlaybackRanges() +{ + // defaults to the whole media + qint64 start = 0; + qint64 end = qint64(m_duration / 10000); + + if (m_netsourceStatistics) { + PROPVARIANT var; + PropVariantInit(&var); + PROPERTYKEY key; + key.fmtid = MFNETSOURCE_STATISTICS; + key.pid = MFNETSOURCE_SEEKRANGESTART_ID; + // GetValue returns S_FALSE if the property is not available, which has + // a value > 0. We therefore can't use the SUCCEEDED macro here. + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + start = qint64(var.uhVal.QuadPart / 10000); + PropVariantClear(&var); + PropVariantInit(&var); + key.pid = MFNETSOURCE_SEEKRANGEEND_ID; + if (m_netsourceStatistics->GetValue(key, &var) == S_OK) { + end = qint64(var.uhVal.QuadPart / 10000); + PropVariantClear(&var); + } + } + } + + return QMediaTimeRange(start, end); +} + +HRESULT MFPlayerSession::QueryInterface(REFIID riid, void** ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFAsyncCallback) { + *ppvObject = static_cast<IMFAsyncCallback*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + return S_OK; +} + +ULONG MFPlayerSession::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG MFPlayerSession::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + this->deleteLater(); + return cRef; +} + +HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult) +{ + if (pResult->GetStateNoAddRef() != m_session) + return S_OK; + + IMFMediaEvent *pEvent = NULL; + // Get the event from the event queue. + HRESULT hr = m_session->EndGetEvent(pResult, &pEvent); + if (FAILED(hr)) { + return S_OK; + } + + MediaEventType meType = MEUnknown; + hr = pEvent->GetType(&meType); + if (FAILED(hr)) { + pEvent->Release(); + return S_OK; + } + + if (meType == MESessionClosed) { + SetEvent(m_hCloseEvent); + pEvent->Release(); + return S_OK; + } else { + hr = m_session->BeginGetEvent(this, m_session); + if (FAILED(hr)) { + pEvent->Release(); + return S_OK; + } + } + + if (!m_closing) { + emit sessionEvent(pEvent); + } else { + pEvent->Release(); + } + return S_OK; +} + +void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) +{ + HRESULT hrStatus = S_OK; + HRESULT hr = sessionEvent->GetStatus(&hrStatus); + if (FAILED(hr) || !m_session) { + sessionEvent->Release(); + return; + } + + MediaEventType meType = MEUnknown; + hr = sessionEvent->GetType(&meType); + +#ifdef DEBUG_MEDIAFOUNDATION + if (FAILED(hrStatus)) + qDebug() << "handleSessionEvent: MediaEventType = " << meType << "Failed"; + else + qDebug() << "handleSessionEvent: MediaEventType = " << meType; +#endif + + switch (meType) { + case MENonFatalError: { + PROPVARIANT var; + PropVariantInit(&var); + sessionEvent->GetValue(&var); + qWarning() << "handleSessionEvent: non fatal error = " << var.ulVal; + PropVariantClear(&var); + emit error(QMediaPlayer::ResourceError, tr("Media session non-fatal error."), false); + } + break; + case MESourceUnknown: + changeStatus(QMediaPlayer::InvalidMedia); + break; + case MEError: + changeStatus(QMediaPlayer::UnknownMediaStatus); + qWarning() << "handleSessionEvent: serious error = " << hrStatus; + emit error(QMediaPlayer::ResourceError, tr("Media session serious error."), true); + break; + case MESessionRateChanged: + // If the rate change succeeded, we've already got the rate + // cached. If it failed, try to get the actual rate. + if (FAILED(hrStatus)) { + PROPVARIANT var; + PropVariantInit(&var); + if (SUCCEEDED(sessionEvent->GetValue(&var)) && (var.vt == VT_R4)) { + m_state.rate = var.fltVal; + } + emit playbackRateChanged(playbackRate()); + } + break; + case MESessionScrubSampleComplete : + if (m_scrubbing) + updatePendingCommands(CmdStart); + break; + case MESessionStarted: + if (m_status == QMediaPlayer::EndOfMedia + || m_status == QMediaPlayer::LoadedMedia) { + // If the session started, then enough data is buffered to play + changeStatus(QMediaPlayer::BufferedMedia); + } + + updatePendingCommands(CmdStart); + // playback started, we can now set again the procAmpValues if they have been + // changed previously (these are lost when loading a new media) + if (m_playerService->videoWindowControl()) { + m_playerService->videoWindowControl()->applyImageControls(); + } + break; + case MESessionStopped: + if (m_status != QMediaPlayer::EndOfMedia) { + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = 0; + + // Reset to Loaded status unless we are loading a new media + // or changing the playback rate to negative values (stop required) + if (m_status != QMediaPlayer::LoadingMedia && m_request.command != CmdSeekResume) + changeStatus(QMediaPlayer::LoadedMedia); + } + updatePendingCommands(CmdStop); + break; + case MESessionPaused: + updatePendingCommands(CmdPause); + break; + case MEReconnectStart: +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MEReconnectStart" << ((hrStatus == S_OK) ? "OK" : "Failed"); +#endif + break; + case MEReconnectEnd: +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MEReconnectEnd" << ((hrStatus == S_OK) ? "OK" : "Failed"); +#endif + break; + case MESessionTopologySet: + if (FAILED(hrStatus)) { + changeStatus(QMediaPlayer::InvalidMedia); + emit error(QMediaPlayer::FormatError, tr("Unsupported media, a codec is missing."), true); + } else { + if (m_audioSampleGrabberNode) { + IUnknown *obj = 0; + if (SUCCEEDED(m_audioSampleGrabberNode->GetObject(&obj))) { + IMFStreamSink *streamSink = 0; + if (SUCCEEDED(obj->QueryInterface(IID_PPV_ARGS(&streamSink)))) { + IMFMediaTypeHandler *typeHandler = 0; + if (SUCCEEDED(streamSink->GetMediaTypeHandler((&typeHandler)))) { + IMFMediaType *mediaType = 0; + if (SUCCEEDED(typeHandler->GetCurrentMediaType(&mediaType))) { + m_audioSampleGrabber->setFormat(audioFormatForMFMediaType(mediaType)); + mediaType->Release(); + } + typeHandler->Release(); + } + streamSink->Release(); + } + obj->Release(); + } + } + + // Topology is resolved and successfuly set, this happens only after loading a new media. + // Make sure we always start the media from the beginning + m_varStart.vt = VT_I8; + m_varStart.hVal.QuadPart = 0; + + changeStatus(QMediaPlayer::LoadedMedia); + } + break; + } + + if (FAILED(hrStatus)) { + sessionEvent->Release(); + return; + } + + switch (meType) { + case MEBufferingStarted: + changeStatus(QMediaPlayer::StalledMedia); + emit bufferStatusChanged(bufferStatus()); + break; + case MEBufferingStopped: + changeStatus(QMediaPlayer::BufferedMedia); + emit bufferStatusChanged(bufferStatus()); + break; + case MESessionEnded: + m_pendingState = NoPending; + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + + m_varStart.vt = VT_I8; + //keep reporting the final position after end of media + m_varStart.hVal.QuadPart = m_duration; + emit positionChanged(position()); + + changeStatus(QMediaPlayer::EndOfMedia); + break; + case MEEndOfPresentationSegment: + break; + case MESessionTopologyStatus: { + UINT32 status; + if (SUCCEEDED(sessionEvent->GetUINT32(MF_EVENT_TOPOLOGY_STATUS, &status))) { + if (status == MF_TOPOSTATUS_READY) { + IMFClock* clock; + if (SUCCEEDED(m_session->GetClock(&clock))) { + clock->QueryInterface(IID_IMFPresentationClock, (void**)(&m_presentationClock)); + clock->Release(); + } + + if (SUCCEEDED(MFGetService(m_session, MF_RATE_CONTROL_SERVICE, IID_PPV_ARGS(&m_rateControl)))) { + if (SUCCEEDED(MFGetService(m_session, MF_RATE_CONTROL_SERVICE, IID_PPV_ARGS(&m_rateSupport)))) { + if ((m_mediaTypes & Video) == Video + && SUCCEEDED(m_rateSupport->IsRateSupported(TRUE, 0, NULL))) + m_canScrub = true; + } + BOOL isThin = FALSE; + float rate = 1; + if (SUCCEEDED(m_rateControl->GetRate(&isThin, &rate))) { + if (m_pendingRate != rate) { + m_state.rate = m_request.rate = rate; + setPlaybackRate(m_pendingRate); + } + } + } + MFGetService(m_session, MFNETSOURCE_STATISTICS_SERVICE, IID_PPV_ARGS(&m_netsourceStatistics)); + + if (SUCCEEDED(MFGetService(m_session, MR_STREAM_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl)))) + setVolumeInternal(m_muted ? 0 : m_volume); + } + } + } + break; + default: + break; + } + + sessionEvent->Release(); +} + +void MFPlayerSession::updatePendingCommands(Command command) +{ + emit positionChanged(position()); + if (m_state.command != command || m_pendingState == NoPending) + return; + + // Seek while paused completed + if (m_pendingState == SeekPending && m_state.prevCmd == CmdPause) { + m_pendingState = NoPending; + // A seek operation actually restarts playback. If scrubbing is possible, playback rate + // is set to 0.0 at this point and we just need to reset the current state to Pause. + // If scrubbing is not possible, the playback rate was not changed and we explicitly need + // to re-pause playback. + if (!canScrub()) + pause(); + else + m_state.setCommand(CmdPause); + } + + m_pendingState = NoPending; + + //First look for rate changes. + if (m_request.rate != m_state.rate) { + commitRateChange(m_request.rate, m_request.isThin); + } + + // Now look for new requests. + if (m_pendingState == NoPending) { + switch (m_request.command) { + case CmdStart: + start(); + break; + case CmdPause: + pause(); + break; + case CmdStop: + stop(); + break; + case CmdSeek: + case CmdSeekResume: + setPositionInternal(m_request.start, m_request.command); + break; + case CmdStartAndSeek: + start(); + setPositionInternal(m_request.start, m_request.command); + break; + } + m_request.setCommand(CmdNone); + } + +} + +bool MFPlayerSession::canScrub() const +{ + return m_canScrub && m_rateSupport && m_rateControl; +} + +void MFPlayerSession::clear() +{ +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MFPlayerSession::clear"; +#endif + m_mediaTypes = 0; + m_canScrub = false; + + m_pendingState = NoPending; + m_state.command = CmdStop; + m_state.prevCmd = CmdNone; + m_request.command = CmdNone; + m_request.prevCmd = CmdNone; + + if (m_presentationClock) { + m_presentationClock->Release(); + m_presentationClock = NULL; + } + if (m_rateControl) { + m_rateControl->Release(); + m_rateControl = NULL; + } + if (m_rateSupport) { + m_rateSupport->Release(); + m_rateSupport = NULL; + } + if (m_volumeControl) { + m_volumeControl->Release(); + m_volumeControl = NULL; + } + if (m_netsourceStatistics) { + m_netsourceStatistics->Release(); + m_netsourceStatistics = NULL; + } + if (m_audioSampleGrabberNode) { + m_audioSampleGrabberNode->Release(); + m_audioSampleGrabberNode = NULL; + } +} diff --git a/src/multimedia/platform/wmf/player/mfplayersession_p.h b/src/multimedia/platform/wmf/player/mfplayersession_p.h new file mode 100644 index 000000000..92f645017 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfplayersession_p.h @@ -0,0 +1,250 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFPLAYERSESSION_H +#define MFPLAYERSESSION_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 <mfapi.h> +#include <mfidl.h> + +#include "qmediaplayer.h" +#include "qmediaservice.h" +#include "qmediatimerange.h" + +#include <QtCore/qcoreevent.h> +#include <QtCore/qmutex.h> +#include <QtCore/qurl.h> +#include <QtCore/qwaitcondition.h> +#include <QtMultimedia/qaudioformat.h> +#include <QtMultimedia/qvideosurfaceformat.h> + +QT_BEGIN_NAMESPACE +class QUrl; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class SourceResolver; +class MFAudioEndpointControl; +class MFVideoRendererControl; +class MFPlayerControl; +class MFMetaDataControl; +class MFPlayerService; +class AudioSampleGrabberCallback; +class MFTransform; +class MFAudioProbeControl; +class MFVideoProbeControl; + +class MFPlayerSession : public QObject, public IMFAsyncCallback +{ + Q_OBJECT + friend class SourceResolver; +public: + MFPlayerSession(MFPlayerService *playerService = 0); + ~MFPlayerSession(); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + STDMETHODIMP Invoke(IMFAsyncResult *pResult); + + STDMETHODIMP GetParameters(DWORD *pdwFlags, DWORD *pdwQueue) + { + Q_UNUSED(pdwFlags); + Q_UNUSED(pdwQueue); + return E_NOTIMPL; + } + + void load(const QUrl &media, QIODevice *stream); + void stop(bool immediate = false); + void start(); + void pause(); + + QMediaPlayer::MediaStatus status() const; + qint64 position(); + void setPosition(qint64 position); + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + int volume() const; + void setVolume(int volume); + bool isMuted() const; + void setMuted(bool muted); + int bufferStatus(); + QMediaTimeRange availablePlaybackRanges(); + + void changeStatus(QMediaPlayer::MediaStatus newStatus); + + void close(); + + void addProbe(MFAudioProbeControl* probe); + void removeProbe(MFAudioProbeControl* probe); + void addProbe(MFVideoProbeControl* probe); + void removeProbe(MFVideoProbeControl* probe); + +Q_SIGNALS: + void error(QMediaPlayer::Error error, QString errorString, bool isFatal); + void sessionEvent(IMFMediaEvent *sessionEvent); + void statusChanged(); + void audioAvailable(); + void videoAvailable(); + void durationUpdate(qint64 duration); + void seekableUpdate(bool seekable); + void positionChanged(qint64 position); + void playbackRateChanged(qreal rate); + void volumeChanged(int volume); + void mutedChanged(bool muted); + void bufferStatusChanged(int percentFilled); + +private Q_SLOTS: + void handleMediaSourceReady(); + void handleSessionEvent(IMFMediaEvent *sessionEvent); + void handleSourceError(long hr); + +private: + long m_cRef; + MFPlayerService *m_playerService; + IMFMediaSession *m_session; + IMFPresentationClock *m_presentationClock; + IMFRateControl *m_rateControl; + IMFRateSupport *m_rateSupport; + IMFAudioStreamVolume *m_volumeControl; + IPropertyStore *m_netsourceStatistics; + PROPVARIANT m_varStart; + UINT64 m_duration; + + enum Command + { + CmdNone = 0, + CmdStop, + CmdStart, + CmdPause, + CmdSeek, + CmdSeekResume, + CmdStartAndSeek + }; + + void clear(); + void setPositionInternal(qint64 position, Command requestCmd); + void setPlaybackRateInternal(qreal rate); + void commitRateChange(qreal rate, BOOL isThin); + bool canScrub() const; + void scrub(bool enableScrub); + bool m_scrubbing; + float m_restoreRate; + + SourceResolver *m_sourceResolver; + HANDLE m_hCloseEvent; + bool m_closing; + + enum MediaType + { + Unknown = 0, + Audio = 1, + Video = 2, + }; + DWORD m_mediaTypes; + + enum PendingState + { + NoPending = 0, + CmdPending, + SeekPending, + }; + + struct SeekState + { + void setCommand(Command cmd) { + prevCmd = command; + command = cmd; + } + Command command; + Command prevCmd; + float rate; // Playback rate + BOOL isThin; // Thinned playback? + qint64 start; // Start position + }; + SeekState m_state; // Current nominal state. + SeekState m_request; // Pending request. + PendingState m_pendingState; + float m_pendingRate; + void updatePendingCommands(Command command); + + QMediaPlayer::MediaStatus m_status; + bool m_canScrub; + int m_volume; + bool m_muted; + + void setVolumeInternal(int volume); + + void createSession(); + void setupPlaybackTopology(IMFMediaSource *source, IMFPresentationDescriptor *sourcePD); + MediaType getStreamType(IMFStreamDescriptor *stream) const; + IMFTopologyNode* addSourceNode(IMFTopology* topology, IMFMediaSource* source, + IMFPresentationDescriptor* presentationDesc, IMFStreamDescriptor *streamDesc); + IMFTopologyNode* addOutputNode(MediaType mediaType, IMFTopology* topology, DWORD sinkID); + + bool addAudioSampleGrabberNode(IMFTopology* topology); + bool setupAudioSampleGrabber(IMFTopology *topology, IMFTopologyNode *sourceNode, IMFTopologyNode *outputNode); + QAudioFormat audioFormatForMFMediaType(IMFMediaType *mediaType) const; + AudioSampleGrabberCallback *m_audioSampleGrabber; + IMFTopologyNode *m_audioSampleGrabberNode; + + IMFTopology *insertMFT(IMFTopology *topology, TOPOID outputNodeId); + bool insertResizer(IMFTopology *topology); + void insertColorConverter(IMFTopology *topology, TOPOID outputNodeId); + MFTransform *m_videoProbeMFT; + QList<MFVideoProbeControl*> m_videoProbes; +}; + + +#endif diff --git a/src/multimedia/platform/wmf/player/mftvideo.cpp b/src/multimedia/platform/wmf/player/mftvideo.cpp new file mode 100644 index 000000000..b2ff27f80 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mftvideo.cpp @@ -0,0 +1,753 @@ +/**************************************************************************** +** +** 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 "mftvideo_p.h" +#include "mfvideoprobecontrol_p.h" +#include <private/qmemoryvideobuffer_p.h> +#include <mferror.h> +#include <strmif.h> +#include <uuids.h> +#include <InitGuid.h> +#include <d3d9.h> +#include <qdebug.h> + +// This MFT sends all samples it processes to connected video probes. +// Sample is sent to probes in ProcessInput. +// In ProcessOutput this MFT simply returns the original sample. + +// The implementation is based on a boilerplate from the MF SDK example. + +MFTransform::MFTransform(): + m_cRef(1), + m_inputType(0), + m_outputType(0), + m_sample(0), + m_videoSinkTypeHandler(0), + m_bytesPerLine(0) +{ +} + +MFTransform::~MFTransform() +{ + if (m_inputType) + m_inputType->Release(); + + if (m_outputType) + m_outputType->Release(); + + if (m_videoSinkTypeHandler) + m_videoSinkTypeHandler->Release(); +} + +void MFTransform::addProbe(MFVideoProbeControl *probe) +{ + QMutexLocker locker(&m_videoProbeMutex); + + if (m_videoProbes.contains(probe)) + return; + + m_videoProbes.append(probe); +} + +void MFTransform::removeProbe(MFVideoProbeControl *probe) +{ + QMutexLocker locker(&m_videoProbeMutex); + m_videoProbes.removeOne(probe); +} + +void MFTransform::setVideoSink(IUnknown *videoSink) +{ + // This transform supports the same input types as the video sink. + // Store its type handler interface in order to report the correct supported types. + + if (m_videoSinkTypeHandler) { + m_videoSinkTypeHandler->Release(); + m_videoSinkTypeHandler = NULL; + } + + if (videoSink) + videoSink->QueryInterface(IID_PPV_ARGS(&m_videoSinkTypeHandler)); +} + +STDMETHODIMP MFTransform::QueryInterface(REFIID riid, void** ppv) +{ + if (!ppv) + return E_POINTER; + if (riid == IID_IMFTransform) { + *ppv = static_cast<IMFTransform*>(this); + } else if (riid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) MFTransform::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) MFTransform::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; +} + +STDMETHODIMP MFTransform::GetStreamLimits(DWORD *pdwInputMinimum, DWORD *pdwInputMaximum, DWORD *pdwOutputMinimum, DWORD *pdwOutputMaximum) +{ + if (!pdwInputMinimum || !pdwInputMaximum || !pdwOutputMinimum || !pdwOutputMaximum) + return E_POINTER; + *pdwInputMinimum = 1; + *pdwInputMaximum = 1; + *pdwOutputMinimum = 1; + *pdwOutputMaximum = 1; + return S_OK; +} + +STDMETHODIMP MFTransform::GetStreamCount(DWORD *pcInputStreams, DWORD *pcOutputStreams) +{ + if (!pcInputStreams || !pcOutputStreams) + return E_POINTER; + + *pcInputStreams = 1; + *pcOutputStreams = 1; + return S_OK; +} + +STDMETHODIMP MFTransform::GetStreamIDs(DWORD dwInputIDArraySize, DWORD *pdwInputIDs, DWORD dwOutputIDArraySize, DWORD *pdwOutputIDs) +{ + // streams are numbered consecutively + Q_UNUSED(dwInputIDArraySize); + Q_UNUSED(pdwInputIDs); + Q_UNUSED(dwOutputIDArraySize); + Q_UNUSED(pdwOutputIDs); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::GetInputStreamInfo(DWORD dwInputStreamID, MFT_INPUT_STREAM_INFO *pStreamInfo) +{ + QMutexLocker locker(&m_mutex); + + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (!pStreamInfo) + return E_POINTER; + + pStreamInfo->cbSize = 0; + pStreamInfo->hnsMaxLatency = 0; + pStreamInfo->cbMaxLookahead = 0; + pStreamInfo->cbAlignment = 0; + pStreamInfo->dwFlags = MFT_INPUT_STREAM_WHOLE_SAMPLES + | MFT_INPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER + | MFT_INPUT_STREAM_PROCESSES_IN_PLACE; + + return S_OK; +} + +STDMETHODIMP MFTransform::GetOutputStreamInfo(DWORD dwOutputStreamID, MFT_OUTPUT_STREAM_INFO *pStreamInfo) +{ + QMutexLocker locker(&m_mutex); + + if (dwOutputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (!pStreamInfo) + return E_POINTER; + + pStreamInfo->cbSize = 0; + pStreamInfo->cbAlignment = 0; + pStreamInfo->dwFlags = MFT_OUTPUT_STREAM_WHOLE_SAMPLES + | MFT_OUTPUT_STREAM_SINGLE_SAMPLE_PER_BUFFER + | MFT_OUTPUT_STREAM_PROVIDES_SAMPLES + | MFT_OUTPUT_STREAM_DISCARDABLE; + + return S_OK; +} + +STDMETHODIMP MFTransform::GetAttributes(IMFAttributes **pAttributes) +{ + // This MFT does not support attributes. + Q_UNUSED(pAttributes); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::GetInputStreamAttributes(DWORD dwInputStreamID, IMFAttributes **pAttributes) +{ + // This MFT does not support input stream attributes. + Q_UNUSED(dwInputStreamID); + Q_UNUSED(pAttributes); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::GetOutputStreamAttributes(DWORD dwOutputStreamID, IMFAttributes **pAttributes) +{ + // This MFT does not support output stream attributes. + Q_UNUSED(dwOutputStreamID); + Q_UNUSED(pAttributes); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::DeleteInputStream(DWORD dwStreamID) +{ + // This MFT has a fixed number of input streams. + Q_UNUSED(dwStreamID); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::AddInputStreams(DWORD cStreams, DWORD *adwStreamIDs) +{ + // This MFT has a fixed number of input streams. + Q_UNUSED(cStreams); + Q_UNUSED(adwStreamIDs); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::GetInputAvailableType(DWORD dwInputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType) +{ + // We support the same input types as the video sink + if (!m_videoSinkTypeHandler) + return E_NOTIMPL; + + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (!ppType) + return E_POINTER; + + return m_videoSinkTypeHandler->GetMediaTypeByIndex(dwTypeIndex, ppType); +} + +STDMETHODIMP MFTransform::GetOutputAvailableType(DWORD dwOutputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType) +{ + // Since we don't modify the samples, the output type must be the same as the input type. + // Report our input type as the only available output type. + + if (dwOutputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (!ppType) + return E_POINTER; + + // Input type must be set first + if (!m_inputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (dwTypeIndex > 0) + return MF_E_NO_MORE_TYPES; + + // Return a copy to make sure our type is not modified + if (FAILED(MFCreateMediaType(ppType))) + return E_OUTOFMEMORY; + + return m_inputType->CopyAllItems(*ppType); +} + +STDMETHODIMP MFTransform::SetInputType(DWORD dwInputStreamID, IMFMediaType *pType, DWORD dwFlags) +{ + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + QMutexLocker locker(&m_mutex); + + if (m_sample) + return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING; + + if (!isMediaTypeSupported(pType)) + return MF_E_INVALIDMEDIATYPE; + + if (dwFlags == MFT_SET_TYPE_TEST_ONLY) + return pType ? S_OK : E_POINTER; + + if (m_inputType) { + m_inputType->Release(); + // Input type has changed, discard output type (if it's set) so it's reset later on + DWORD flags = 0; + if (m_outputType && m_outputType->IsEqual(pType, &flags) != S_OK) { + m_outputType->Release(); + m_outputType = 0; + } + } + + m_inputType = pType; + + if (m_inputType) + m_inputType->AddRef(); + + return S_OK; +} + +STDMETHODIMP MFTransform::SetOutputType(DWORD dwOutputStreamID, IMFMediaType *pType, DWORD dwFlags) +{ + if (dwOutputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (dwFlags == MFT_SET_TYPE_TEST_ONLY && !pType) + return E_POINTER; + + QMutexLocker locker(&m_mutex); + + // Input type must be set first + if (!m_inputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (m_sample) + return MF_E_TRANSFORM_CANNOT_CHANGE_MEDIATYPE_WHILE_PROCESSING; + + DWORD flags = 0; + if (pType && m_inputType->IsEqual(pType, &flags) != S_OK) + return MF_E_INVALIDMEDIATYPE; + + if (dwFlags == MFT_SET_TYPE_TEST_ONLY) + return pType ? S_OK : E_POINTER; + + if (m_outputType) + m_outputType->Release(); + + m_outputType = pType; + + if (m_outputType) { + m_outputType->AddRef(); + m_format = videoFormatForMFMediaType(m_outputType, &m_bytesPerLine); + } + + return S_OK; +} + +STDMETHODIMP MFTransform::GetInputCurrentType(DWORD dwInputStreamID, IMFMediaType **ppType) +{ + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (ppType == NULL) + return E_POINTER; + + QMutexLocker locker(&m_mutex); + + if (!m_inputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + // Return a copy to make sure our type is not modified + if (FAILED(MFCreateMediaType(ppType))) + return E_OUTOFMEMORY; + + return m_inputType->CopyAllItems(*ppType); +} + +STDMETHODIMP MFTransform::GetOutputCurrentType(DWORD dwOutputStreamID, IMFMediaType **ppType) +{ + if (dwOutputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (ppType == NULL) + return E_POINTER; + + QMutexLocker locker(&m_mutex); + + if (!m_outputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + // Return a copy to make sure our type is not modified + if (FAILED(MFCreateMediaType(ppType))) + return E_OUTOFMEMORY; + + return m_outputType->CopyAllItems(*ppType); +} + +STDMETHODIMP MFTransform::GetInputStatus(DWORD dwInputStreamID, DWORD *pdwFlags) +{ + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (!pdwFlags) + return E_POINTER; + + QMutexLocker locker(&m_mutex); + + if (!m_inputType || !m_outputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (m_sample) + *pdwFlags = 0; + else + *pdwFlags = MFT_INPUT_STATUS_ACCEPT_DATA; + + return S_OK; +} + +STDMETHODIMP MFTransform::GetOutputStatus(DWORD *pdwFlags) +{ + if (!pdwFlags) + return E_POINTER; + + QMutexLocker locker(&m_mutex); + + if (!m_inputType || !m_outputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (m_sample) + *pdwFlags = MFT_OUTPUT_STATUS_SAMPLE_READY; + else + *pdwFlags = 0; + + return S_OK; +} + +STDMETHODIMP MFTransform::SetOutputBounds(LONGLONG hnsLowerBound, LONGLONG hnsUpperBound) +{ + Q_UNUSED(hnsLowerBound); + Q_UNUSED(hnsUpperBound); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::ProcessEvent(DWORD dwInputStreamID, IMFMediaEvent *pEvent) +{ + // This MFT ignores all events, and the pipeline should send all events downstream. + Q_UNUSED(dwInputStreamID); + Q_UNUSED(pEvent); + return E_NOTIMPL; +} + +STDMETHODIMP MFTransform::ProcessMessage(MFT_MESSAGE_TYPE eMessage, ULONG_PTR ulParam) +{ + Q_UNUSED(ulParam); + + HRESULT hr = S_OK; + + switch (eMessage) + { + case MFT_MESSAGE_COMMAND_FLUSH: + hr = OnFlush(); + break; + + case MFT_MESSAGE_COMMAND_DRAIN: + // Drain: Tells the MFT not to accept any more input until + // all of the pending output has been processed. That is our + // default behevior already, so there is nothing to do. + break; + + case MFT_MESSAGE_SET_D3D_MANAGER: + // The pipeline should never send this message unless the MFT + // has the MF_SA_D3D_AWARE attribute set to TRUE. However, if we + // do get this message, it's invalid and we don't implement it. + hr = E_NOTIMPL; + break; + + // The remaining messages do not require any action from this MFT. + case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING: + case MFT_MESSAGE_NOTIFY_END_STREAMING: + case MFT_MESSAGE_NOTIFY_END_OF_STREAM: + case MFT_MESSAGE_NOTIFY_START_OF_STREAM: + break; + } + + return hr; +} + +STDMETHODIMP MFTransform::ProcessInput(DWORD dwInputStreamID, IMFSample *pSample, DWORD dwFlags) +{ + if (dwInputStreamID > 0) + return MF_E_INVALIDSTREAMNUMBER; + + if (dwFlags != 0) + return E_INVALIDARG; // dwFlags is reserved and must be zero. + + QMutexLocker locker(&m_mutex); + + if (!m_inputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (m_sample) + return MF_E_NOTACCEPTING; + + // Validate the number of buffers. There should only be a single buffer to hold the video frame. + DWORD dwBufferCount = 0; + HRESULT hr = pSample->GetBufferCount(&dwBufferCount); + if (FAILED(hr)) + return hr; + + if (dwBufferCount == 0) + return E_FAIL; + + if (dwBufferCount > 1) + return MF_E_SAMPLE_HAS_TOO_MANY_BUFFERS; + + m_sample = pSample; + m_sample->AddRef(); + + QMutexLocker lockerProbe(&m_videoProbeMutex); + + if (!m_videoProbes.isEmpty()) { + QVideoFrame frame = makeVideoFrame(); + + for (MFVideoProbeControl* probe : qAsConst(m_videoProbes)) + probe->bufferProbed(frame); + } + + return S_OK; +} + +STDMETHODIMP MFTransform::ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, MFT_OUTPUT_DATA_BUFFER *pOutputSamples, DWORD *pdwStatus) +{ + if (pOutputSamples == NULL || pdwStatus == NULL) + return E_POINTER; + + if (cOutputBufferCount != 1) + return E_INVALIDARG; + + QMutexLocker locker(&m_mutex); + + if (!m_inputType) + return MF_E_TRANSFORM_TYPE_NOT_SET; + + if (!m_outputType) { + pOutputSamples[0].dwStatus = MFT_OUTPUT_DATA_BUFFER_FORMAT_CHANGE; + return MF_E_TRANSFORM_STREAM_CHANGE; + } + + IMFMediaBuffer *input = NULL; + IMFMediaBuffer *output = NULL; + + if (dwFlags == MFT_PROCESS_OUTPUT_DISCARD_WHEN_NO_BUFFER) + goto done; + else if (dwFlags != 0) + return E_INVALIDARG; + + if (!m_sample) + return MF_E_TRANSFORM_NEED_MORE_INPUT; + + // Since the MFT_OUTPUT_STREAM_PROVIDES_SAMPLES flag is set, the client + // should not be providing samples here + if (pOutputSamples[0].pSample != NULL) + return E_INVALIDARG; + + pOutputSamples[0].pSample = m_sample; + pOutputSamples[0].pSample->AddRef(); + + // Send video frame to probes + // We do it here (instead of inside ProcessInput) to make sure samples discarded by the renderer + // are not sent. + m_videoProbeMutex.lock(); + if (!m_videoProbes.isEmpty()) { + QVideoFrame frame = makeVideoFrame(); + + for (MFVideoProbeControl* probe : qAsConst(m_videoProbes)) + probe->bufferProbed(frame); + } + m_videoProbeMutex.unlock(); + +done: + pOutputSamples[0].dwStatus = 0; + *pdwStatus = 0; + + m_sample->Release(); + m_sample = 0; + + if (input) + input->Release(); + if (output) + output->Release(); + + return S_OK; +} + +HRESULT MFTransform::OnFlush() +{ + QMutexLocker locker(&m_mutex); + + if (m_sample) { + m_sample->Release(); + m_sample = 0; + } + return S_OK; +} + +QVideoFrame::PixelFormat MFTransform::formatFromSubtype(const GUID& subtype) +{ + if (subtype == MFVideoFormat_ARGB32) + return QVideoFrame::Format_ARGB32; + else if (subtype == MFVideoFormat_RGB32) + return QVideoFrame::Format_RGB32; + else if (subtype == MFVideoFormat_RGB24) + return QVideoFrame::Format_RGB24; + else if (subtype == MFVideoFormat_RGB565) + return QVideoFrame::Format_RGB565; + else if (subtype == MFVideoFormat_RGB555) + return QVideoFrame::Format_RGB555; + else if (subtype == MFVideoFormat_AYUV) + return QVideoFrame::Format_AYUV444; + else if (subtype == MFVideoFormat_I420) + return QVideoFrame::Format_YUV420P; + else if (subtype == MFVideoFormat_UYVY) + return QVideoFrame::Format_UYVY; + else if (subtype == MFVideoFormat_YV12) + return QVideoFrame::Format_YV12; + else if (subtype == MFVideoFormat_NV12) + return QVideoFrame::Format_NV12; + + return QVideoFrame::Format_Invalid; +} + +QVideoSurfaceFormat MFTransform::videoFormatForMFMediaType(IMFMediaType *mediaType, int *bytesPerLine) +{ + UINT32 stride; + if (FAILED(mediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, &stride))) { + *bytesPerLine = 0; + return QVideoSurfaceFormat(); + } + + *bytesPerLine = (int)stride; + + QSize size; + UINT32 width, height; + if (FAILED(MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, &width, &height))) + return QVideoSurfaceFormat(); + + size.setWidth(width); + size.setHeight(height); + + GUID subtype = GUID_NULL; + if (FAILED(mediaType->GetGUID(MF_MT_SUBTYPE, &subtype))) + return QVideoSurfaceFormat(); + + QVideoFrame::PixelFormat pixelFormat = formatFromSubtype(subtype); + QVideoSurfaceFormat format(size, pixelFormat); + + UINT32 num, den; + if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_PIXEL_ASPECT_RATIO, &num, &den))) { + format.setPixelAspectRatio(num, den); + } + if (SUCCEEDED(MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &num, &den))) { + format.setFrameRate(qreal(num)/den); + } + + return format; +} + +QVideoFrame MFTransform::makeVideoFrame() +{ + QVideoFrame frame; + + if (!m_format.isValid()) + return frame; + + IMFMediaBuffer *buffer = 0; + + do { + if (FAILED(m_sample->ConvertToContiguousBuffer(&buffer))) + break; + + QByteArray array = dataFromBuffer(buffer, m_format.frameHeight(), &m_bytesPerLine); + if (array.isEmpty()) + break; + + // Wrapping IMFSample or IMFMediaBuffer in a QVideoFrame is not possible because we cannot hold + // IMFSample for a "long" time without affecting the rest of the topology. + // If IMFSample is held for more than 5 frames decoder starts to reuse it even though it hasn't been released it yet. + // That is why we copy data from IMFMediaBuffer here. + frame = QVideoFrame(new QMemoryVideoBuffer(array, m_bytesPerLine), m_format.frameSize(), m_format.pixelFormat()); + + // WMF uses 100-nanosecond units, Qt uses microseconds + LONGLONG startTime = -1; + if (SUCCEEDED(m_sample->GetSampleTime(&startTime))) { + frame.setStartTime(startTime * 0.1); + + LONGLONG duration = -1; + if (SUCCEEDED(m_sample->GetSampleDuration(&duration))) + frame.setEndTime((startTime + duration) * 0.1); + } + } while (false); + + if (buffer) + buffer->Release(); + + return frame; +} + +QByteArray MFTransform::dataFromBuffer(IMFMediaBuffer *buffer, int height, int *bytesPerLine) +{ + QByteArray array; + BYTE *bytes; + DWORD length; + HRESULT hr = buffer->Lock(&bytes, NULL, &length); + if (SUCCEEDED(hr)) { + array = QByteArray((const char *)bytes, (int)length); + buffer->Unlock(); + } else { + // try to lock as Direct3DSurface + IDirect3DSurface9 *surface = 0; + do { + if (FAILED(MFGetService(buffer, MR_BUFFER_SERVICE, IID_IDirect3DSurface9, (void**)&surface))) + break; + + D3DLOCKED_RECT rect; + if (FAILED(surface->LockRect(&rect, NULL, D3DLOCK_READONLY))) + break; + + if (bytesPerLine) + *bytesPerLine = (int)rect.Pitch; + + array = QByteArray((const char *)rect.pBits, rect.Pitch * height); + surface->UnlockRect(); + } while (false); + + if (surface) { + surface->Release(); + surface = 0; + } + } + + return array; +} + +bool MFTransform::isMediaTypeSupported(IMFMediaType *type) +{ + // If we don't have the video sink's type handler, + // assume it supports anything... + if (!m_videoSinkTypeHandler || !type) + return true; + + return m_videoSinkTypeHandler->IsMediaTypeSupported(type, NULL) == S_OK; +} diff --git a/src/multimedia/platform/wmf/player/mftvideo_p.h b/src/multimedia/platform/wmf/player/mftvideo_p.h new file mode 100644 index 000000000..e08f0977f --- /dev/null +++ b/src/multimedia/platform/wmf/player/mftvideo_p.h @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** 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 MFTRANSFORM_H +#define MFTRANSFORM_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 <mfapi.h> +#include <mfidl.h> +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtMultimedia/qvideosurfaceformat.h> + +QT_USE_NAMESPACE + +class MFVideoProbeControl; + +class MFTransform: public IMFTransform +{ +public: + MFTransform(); + ~MFTransform(); + + void addProbe(MFVideoProbeControl* probe); + void removeProbe(MFVideoProbeControl* probe); + + void setVideoSink(IUnknown *videoSink); + + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID iid, void** ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IMFTransform methods + STDMETHODIMP GetStreamLimits(DWORD *pdwInputMinimum, DWORD *pdwInputMaximum, DWORD *pdwOutputMinimum, DWORD *pdwOutputMaximum); + STDMETHODIMP GetStreamCount(DWORD *pcInputStreams, DWORD *pcOutputStreams); + STDMETHODIMP GetStreamIDs(DWORD dwInputIDArraySize, DWORD *pdwInputIDs, DWORD dwOutputIDArraySize, DWORD *pdwOutputIDs); + STDMETHODIMP GetInputStreamInfo(DWORD dwInputStreamID, MFT_INPUT_STREAM_INFO *pStreamInfo); + STDMETHODIMP GetOutputStreamInfo(DWORD dwOutputStreamID, MFT_OUTPUT_STREAM_INFO *pStreamInfo); + STDMETHODIMP GetAttributes(IMFAttributes **pAttributes); + STDMETHODIMP GetInputStreamAttributes(DWORD dwInputStreamID, IMFAttributes **pAttributes); + STDMETHODIMP GetOutputStreamAttributes(DWORD dwOutputStreamID, IMFAttributes **pAttributes); + STDMETHODIMP DeleteInputStream(DWORD dwStreamID); + STDMETHODIMP AddInputStreams(DWORD cStreams, DWORD *adwStreamIDs); + STDMETHODIMP GetInputAvailableType(DWORD dwInputStreamID, DWORD dwTypeIndex, IMFMediaType **ppType); + STDMETHODIMP GetOutputAvailableType(DWORD dwOutputStreamID,DWORD dwTypeIndex, IMFMediaType **ppType); + STDMETHODIMP SetInputType(DWORD dwInputStreamID, IMFMediaType *pType, DWORD dwFlags); + STDMETHODIMP SetOutputType(DWORD dwOutputStreamID, IMFMediaType *pType, DWORD dwFlags); + STDMETHODIMP GetInputCurrentType(DWORD dwInputStreamID, IMFMediaType **ppType); + STDMETHODIMP GetOutputCurrentType(DWORD dwOutputStreamID, IMFMediaType **ppType); + STDMETHODIMP GetInputStatus(DWORD dwInputStreamID, DWORD *pdwFlags); + STDMETHODIMP GetOutputStatus(DWORD *pdwFlags); + STDMETHODIMP SetOutputBounds(LONGLONG hnsLowerBound, LONGLONG hnsUpperBound); + STDMETHODIMP ProcessEvent(DWORD dwInputStreamID, IMFMediaEvent *pEvent); + STDMETHODIMP ProcessMessage(MFT_MESSAGE_TYPE eMessage, ULONG_PTR ulParam); + STDMETHODIMP ProcessInput(DWORD dwInputStreamID, IMFSample *pSample, DWORD dwFlags); + STDMETHODIMP ProcessOutput(DWORD dwFlags, DWORD cOutputBufferCount, MFT_OUTPUT_DATA_BUFFER *pOutputSamples, DWORD *pdwStatus); + +private: + HRESULT OnFlush(); + static QVideoFrame::PixelFormat formatFromSubtype(const GUID& subtype); + static QVideoSurfaceFormat videoFormatForMFMediaType(IMFMediaType *mediaType, int *bytesPerLine); + QVideoFrame makeVideoFrame(); + QByteArray dataFromBuffer(IMFMediaBuffer *buffer, int height, int *bytesPerLine); + bool isMediaTypeSupported(IMFMediaType *type); + + long m_cRef; + IMFMediaType *m_inputType; + IMFMediaType *m_outputType; + IMFSample *m_sample; + QMutex m_mutex; + + IMFMediaTypeHandler *m_videoSinkTypeHandler; + + QList<MFVideoProbeControl*> m_videoProbes; + QMutex m_videoProbeMutex; + + QVideoSurfaceFormat m_format; + int m_bytesPerLine; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp b/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp new file mode 100644 index 000000000..e4d1a8b23 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfvideoprobecontrol.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** 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 "mfvideoprobecontrol_p.h" + +MFVideoProbeControl::MFVideoProbeControl(QObject *parent) + : QMediaVideoProbeControl(parent) +{ +} + +MFVideoProbeControl::~MFVideoProbeControl() +{ +} + +void MFVideoProbeControl::bufferProbed(const QVideoFrame& frame) +{ + QMetaObject::invokeMethod(this, "videoFrameProbed", Qt::QueuedConnection, Q_ARG(QVideoFrame, frame)); +} diff --git a/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h b/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h new file mode 100644 index 000000000..a7f79ef92 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfvideoprobecontrol_p.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** 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 MFVIDEOPROBECONTROL_H +#define MFVIDEOPROBECONTROL_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> +#include <QtCore/qmutex.h> +#include <qvideoframe.h> + +QT_USE_NAMESPACE + +class MFVideoProbeControl : public QMediaVideoProbeControl +{ + Q_OBJECT +public: + explicit MFVideoProbeControl(QObject *parent); + virtual ~MFVideoProbeControl(); + + void bufferProbed(const QVideoFrame& frame); +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp b/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp new file mode 100644 index 000000000..2acde7e4c --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfvideorenderercontrol.cpp @@ -0,0 +1,2424 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 "mfvideorenderercontrol_p.h" +#include "mfactivate_p.h" + +#include "evrcustompresenter_p.h" + +#include <qabstractvideosurface.h> +#include <qvideosurfaceformat.h> +#include <qtcore/qtimer.h> +#include <qtcore/qmutex.h> +#include <qtcore/qcoreevent.h> +#include <qtcore/qcoreapplication.h> +#include <qtcore/qthread.h> +#include "guiddef.h" +#include <qtcore/qdebug.h> + +//#define DEBUG_MEDIAFOUNDATION +#define PAD_TO_DWORD(x) (((x) + 3) & ~3) + +namespace +{ + class MediaSampleVideoBuffer : public QAbstractVideoBuffer + { + public: + MediaSampleVideoBuffer(IMFMediaBuffer *buffer, int bytesPerLine) + : QAbstractVideoBuffer(NoHandle) + , m_buffer(buffer) + , m_bytesPerLine(bytesPerLine) + , m_mapMode(NotMapped) + { + buffer->AddRef(); + } + + ~MediaSampleVideoBuffer() + { + m_buffer->Release(); + } + + MapData map(MapMode mode) override + { + MapData mapData; + if (m_mapMode == NotMapped && mode != NotMapped) { + BYTE *bytes; + DWORD length; + HRESULT hr = m_buffer->Lock(&bytes, NULL, &length); + if (SUCCEEDED(hr)) { + mapData.nBytes = qsizetype(length); + mapData.nPlanes = 1; + mapData.bytesPerLine[0] = m_bytesPerLine; + mapData.data[0] = reinterpret_cast<uchar *>(bytes); + m_mapMode = mode; + } else { + qWarning("Faild to lock mf buffer!"); + } + } + return mapData; + } + + void unmap() override + { + if (m_mapMode == NotMapped) + return; + m_mapMode = NotMapped; + m_buffer->Unlock(); + } + + MapMode mapMode() const override + { + return m_mapMode; + } + + private: + IMFMediaBuffer *m_buffer; + int m_bytesPerLine; + MapMode m_mapMode; + }; + + // Custom interface for handling IMFStreamSink::PlaceMarker calls asynchronously. + MIDL_INTERFACE("a3ff32de-1031-438a-8b47-82f8acda59b7") + IMarker : public IUnknown + { + virtual STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) = 0; + virtual STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) = 0; + virtual STDMETHODIMP GetContext(PROPVARIANT *pvar) = 0; + }; + + class Marker : public IMarker + { + public: + static HRESULT Create( + MFSTREAMSINK_MARKER_TYPE eMarkerType, + const PROPVARIANT* pvarMarkerValue, // Can be NULL. + const PROPVARIANT* pvarContextValue, // Can be NULL. + IMarker **ppMarker) + { + if (ppMarker == NULL) + return E_POINTER; + + HRESULT hr = S_OK; + Marker *pMarker = new Marker(eMarkerType); + if (pMarker == NULL) + hr = E_OUTOFMEMORY; + + // Copy the marker data. + if (SUCCEEDED(hr) && pvarMarkerValue) + hr = PropVariantCopy(&pMarker->m_varMarkerValue, pvarMarkerValue); + + if (SUCCEEDED(hr) && pvarContextValue) + hr = PropVariantCopy(&pMarker->m_varContextValue, pvarContextValue); + + if (SUCCEEDED(hr)) { + *ppMarker = pMarker; + (*ppMarker)->AddRef(); + } + + if (pMarker) + pMarker->Release(); + + return hr; + } + + // IUnknown methods. + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if (!ppv) + return E_POINTER; + if (iid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + } else if (iid == __uuidof(IMarker)) { + *ppv = static_cast<IMarker*>(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release() + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + STDMETHODIMP GetMarkerType(MFSTREAMSINK_MARKER_TYPE *pType) + { + if (pType == NULL) + return E_POINTER; + *pType = m_eMarkerType; + return S_OK; + } + + STDMETHODIMP GetMarkerValue(PROPVARIANT *pvar) + { + if (pvar == NULL) + return E_POINTER; + return PropVariantCopy(pvar, &m_varMarkerValue); + } + + STDMETHODIMP GetContext(PROPVARIANT *pvar) + { + if (pvar == NULL) + return E_POINTER; + return PropVariantCopy(pvar, &m_varContextValue); + } + + protected: + MFSTREAMSINK_MARKER_TYPE m_eMarkerType; + PROPVARIANT m_varMarkerValue; + PROPVARIANT m_varContextValue; + + private: + long m_cRef; + + Marker(MFSTREAMSINK_MARKER_TYPE eMarkerType) : m_cRef(1), m_eMarkerType(eMarkerType) + { + PropVariantInit(&m_varMarkerValue); + PropVariantInit(&m_varContextValue); + } + + virtual ~Marker() + { + PropVariantClear(&m_varMarkerValue); + PropVariantClear(&m_varContextValue); + } + }; + + class MediaStream : public QObject, public IMFStreamSink, public IMFMediaTypeHandler + { + Q_OBJECT + friend class MFVideoRendererControl; + public: + static const DWORD DEFAULT_MEDIA_STREAM_ID = 0x0; + + MediaStream(IMFMediaSink *parent, MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_eventQueue(0) + , m_shutdown(false) + , m_surface(0) + , m_state(State_TypeNotSet) + , m_currentFormatIndex(-1) + , m_bytesPerLine(0) + , m_workQueueId(0) + , m_workQueueCB(this, &MediaStream::onDispatchWorkItem) + , m_finalizeResult(0) + , m_scheduledBuffer(0) + , m_bufferStartTime(-1) + , m_bufferDuration(-1) + , m_presentationClock(0) + , m_sampleRequested(false) + , m_currentMediaType(0) + , m_prerolling(false) + , m_prerollTargetTime(0) + , m_startTime(0) + , m_rendererControl(rendererControl) + , m_rate(1.f) + { + m_sink = parent; + + if (FAILED(MFCreateEventQueue(&m_eventQueue))) + qWarning("Failed to create mf event queue!"); + if (FAILED(MFAllocateWorkQueue(&m_workQueueId))) + qWarning("Failed to allocated mf work queue!"); + } + + ~MediaStream() + { + Q_ASSERT(m_shutdown); + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) + { + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFStreamSink) { + *ppvObject = static_cast<IMFStreamSink*>(this); + } else if (riid == IID_IMFMediaEventGenerator) { + *ppvObject = static_cast<IMFMediaEventGenerator*>(this); + } else if (riid == IID_IMFMediaTypeHandler) { + *ppvObject = static_cast<IMFMediaTypeHandler*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(static_cast<IMFStreamSink*>(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + //from IMFMediaEventGenerator + STDMETHODIMP GetEvent( + DWORD dwFlags, + IMFMediaEvent **ppEvent) + { + // GetEvent can block indefinitely, so we don't hold the lock. + // This requires some juggling with the event queue pointer. + HRESULT hr = S_OK; + IMFMediaEventQueue *queue = NULL; + + m_mutex.lock(); + if (m_shutdown) + hr = MF_E_SHUTDOWN; + if (SUCCEEDED(hr)) { + queue = m_eventQueue; + queue->AddRef(); + } + m_mutex.unlock(); + + // Now get the event. + if (SUCCEEDED(hr)) { + hr = queue->GetEvent(dwFlags, ppEvent); + queue->Release(); + } + + return hr; + } + + STDMETHODIMP BeginGetEvent( + IMFAsyncCallback *pCallback, + IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->BeginGetEvent(pCallback, punkState); + } + + STDMETHODIMP EndGetEvent( + IMFAsyncResult *pResult, + IMFMediaEvent **ppEvent) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->EndGetEvent(pResult, ppEvent); + } + + STDMETHODIMP QueueEvent( + MediaEventType met, + REFGUID guidExtendedType, + HRESULT hrStatus, + const PROPVARIANT *pvValue) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::QueueEvent" << met; +#endif + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue); + } + + //from IMFStreamSink + STDMETHODIMP GetMediaSink( + IMFMediaSink **ppMediaSink) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + else if (!ppMediaSink) + return E_INVALIDARG; + + m_sink->AddRef(); + *ppMediaSink = m_sink; + return S_OK; + } + + STDMETHODIMP GetIdentifier( + DWORD *pdwIdentifier) + { + *pdwIdentifier = MediaStream::DEFAULT_MEDIA_STREAM_ID; + return S_OK; + } + + STDMETHODIMP GetMediaTypeHandler( + IMFMediaTypeHandler **ppHandler) + { + LPVOID handler = NULL; + HRESULT hr = QueryInterface(IID_IMFMediaTypeHandler, &handler); + *ppHandler = (IMFMediaTypeHandler*)(handler); + return hr; + } + + STDMETHODIMP ProcessSample( + IMFSample *pSample) + { + if (pSample == NULL) + return E_INVALIDARG; + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (!m_prerolling) { + hr = validateOperation(OpProcessSample); + if (FAILED(hr)) + return hr; + } + + pSample->AddRef(); + m_sampleQueue.push_back(pSample); + + // Unless we are paused, start an async operation to dispatch the next sample. + if (m_state != State_Paused) + hr = queueAsyncOperation(OpProcessSample); + + return hr; + } + + STDMETHODIMP PlaceMarker( + MFSTREAMSINK_MARKER_TYPE eMarkerType, + const PROPVARIANT *pvarMarkerValue, + const PROPVARIANT *pvarContextValue) + { + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + IMarker *pMarker = NULL; + if (m_shutdown) + return MF_E_SHUTDOWN; + + hr = validateOperation(OpPlaceMarker); + if (FAILED(hr)) + return hr; + + // Create a marker object and put it on the sample queue. + hr = Marker::Create(eMarkerType, pvarMarkerValue, pvarContextValue, &pMarker); + if (FAILED(hr)) + return hr; + + m_sampleQueue.push_back(pMarker); + + // Unless we are paused, start an async operation to dispatch the next sample/marker. + if (m_state != State_Paused) + hr = queueAsyncOperation(OpPlaceMarker); // Increments ref count on pOp. + return hr; + } + + STDMETHODIMP Flush( void) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::Flush"; +#endif + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + // Note: Even though we are flushing data, we still need to send + // any marker events that were queued. + clearBufferCache(); + return processSamplesFromQueue(DropSamples); + } + + //from IMFMediaTypeHandler + STDMETHODIMP IsMediaTypeSupported( + IMFMediaType *pMediaType, + IMFMediaType **ppMediaType) + { + if (ppMediaType) + *ppMediaType = NULL; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + int index = getMediaTypeIndex(pMediaType); + if (index < 0) { + if (ppMediaType && m_mediaTypes.size() > 0) { + *ppMediaType = m_mediaTypes[0]; + (*ppMediaType)->AddRef(); + } + return MF_E_INVALIDMEDIATYPE; + } + + BOOL compressed = TRUE; + pMediaType->IsCompressedFormat(&compressed); + if (compressed) { + if (ppMediaType && (SUCCEEDED(MFCreateMediaType(ppMediaType)))) { + (*ppMediaType)->CopyAllItems(pMediaType); + (*ppMediaType)->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE); + (*ppMediaType)->SetUINT32(MF_MT_COMPRESSED, FALSE); + (*ppMediaType)->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + } + return MF_E_INVALIDMEDIATYPE; + } + + return S_OK; + } + + STDMETHODIMP GetMediaTypeCount( + DWORD *pdwTypeCount) + { + if (pdwTypeCount == NULL) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + *pdwTypeCount = DWORD(m_mediaTypes.size()); + return S_OK; + } + + STDMETHODIMP GetMediaTypeByIndex( + DWORD dwIndex, + IMFMediaType **ppType) + { + if (ppType == NULL) + return E_INVALIDARG; + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + hr = MF_E_SHUTDOWN; + + if (SUCCEEDED(hr)) { + if (dwIndex >= DWORD(m_mediaTypes.size())) + hr = MF_E_NO_MORE_TYPES; + } + + if (SUCCEEDED(hr)) { + *ppType = m_mediaTypes[dwIndex]; + (*ppType)->AddRef(); + } + return hr; + } + + STDMETHODIMP SetCurrentMediaType( + IMFMediaType *pMediaType) + { + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + DWORD flag = MF_MEDIATYPE_EQUAL_MAJOR_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_TYPES | + MF_MEDIATYPE_EQUAL_FORMAT_DATA; + + if (m_currentMediaType && (m_currentMediaType->IsEqual(pMediaType, &flag) == S_OK)) + return S_OK; + + hr = validateOperation(OpSetMediaType); + + if (SUCCEEDED(hr)) { + int index = getMediaTypeIndex(pMediaType); + if (index >= 0) { + UINT64 size; + hr = pMediaType->GetUINT64(MF_MT_FRAME_SIZE, &size); + if (SUCCEEDED(hr)) { + m_currentFormatIndex = index; + int width = int(HI32(size)); + int height = int(LO32(size)); + QVideoSurfaceFormat format(QSize(width, height), m_pixelFormats[index]); + m_surfaceFormat = format; + + MFVideoArea viewport; + if (SUCCEEDED(pMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE, + reinterpret_cast<UINT8*>(&viewport), + sizeof(MFVideoArea), + NULL))) { + + m_surfaceFormat.setViewport(QRect(viewport.OffsetX.value, + viewport.OffsetY.value, + viewport.Area.cx, + viewport.Area.cy)); + } + + if (FAILED(pMediaType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&m_bytesPerLine))) { + m_bytesPerLine = getBytesPerLine(format); + } + + m_state = State_Ready; + if (m_currentMediaType) + m_currentMediaType->Release(); + m_currentMediaType = pMediaType; + pMediaType->AddRef(); + } + } else { + hr = MF_E_INVALIDREQUEST; + } + } + return hr; + } + + STDMETHODIMP GetCurrentMediaType( + IMFMediaType **ppMediaType) + { + if (ppMediaType == NULL) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + if (m_currentFormatIndex < 0) + return MF_E_NOT_INITIALIZED; + *ppMediaType = m_currentMediaType; + (*ppMediaType)->AddRef(); + return S_OK; + } + + STDMETHODIMP GetMajorType( + GUID *pguidMajorType) + { + if (pguidMajorType == NULL) + return E_INVALIDARG; + *pguidMajorType = MFMediaType_Video; + return S_OK; + } + + // + void setSurface(QAbstractVideoSurface *surface) + { + m_mutex.lock(); + m_surface = surface; + m_mutex.unlock(); + supportedFormatsChanged(); + } + + void setClock(IMFPresentationClock *presentationClock) + { + QMutexLocker locker(&m_mutex); + if (!m_shutdown) { + if (m_presentationClock) + m_presentationClock->Release(); + m_presentationClock = presentationClock; + if (m_presentationClock) + m_presentationClock->AddRef(); + } + } + + void shutdown() + { + QMutexLocker locker(&m_mutex); + Q_ASSERT(!m_shutdown); + + if (m_currentMediaType) { + m_currentMediaType->Release(); + m_currentMediaType = NULL; + m_currentFormatIndex = -1; + } + + if (m_eventQueue) + m_eventQueue->Shutdown(); + + MFUnlockWorkQueue(m_workQueueId); + + if (m_presentationClock) { + m_presentationClock->Release(); + m_presentationClock = NULL; + } + + clearMediaTypes(); + clearSampleQueue(); + clearBufferCache(); + + if (m_eventQueue) { + m_eventQueue->Release(); + m_eventQueue = NULL; + } + + m_shutdown = true; + } + + HRESULT startPreroll(MFTIME hnsUpcomingStartTime) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpPreroll); + if (SUCCEEDED(hr)) { + m_state = State_Prerolling; + m_prerollTargetTime = hnsUpcomingStartTime; + hr = queueAsyncOperation(OpPreroll); + } + return hr; + } + + HRESULT finalize(IMFAsyncCallback *pCallback, IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + hr = validateOperation(OpFinalize); + if (SUCCEEDED(hr) && m_finalizeResult != NULL) + hr = MF_E_INVALIDREQUEST; // The operation is already pending. + + // Create and store the async result object. + if (SUCCEEDED(hr)) + hr = MFCreateAsyncResult(NULL, pCallback, punkState, &m_finalizeResult); + + if (SUCCEEDED(hr)) { + m_state = State_Finalized; + hr = queueAsyncOperation(OpFinalize); + } + return hr; + } + + HRESULT start(MFTIME start) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::start" << start; +#endif + HRESULT hr = S_OK; + QMutexLocker locker(&m_mutex); + if (m_rate != 0) + hr = validateOperation(OpStart); + + if (SUCCEEDED(hr)) { + MFTIME sysTime; + if (start != PRESENTATION_CURRENT_POSITION) + m_startTime = start; // Cache the start time. + else + m_presentationClock->GetCorrelatedTime(0, &m_startTime, &sysTime); + m_state = State_Started; + hr = queueAsyncOperation(OpStart); + } + return hr; + } + + HRESULT restart() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::restart"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpRestart); + if (SUCCEEDED(hr)) { + m_state = State_Started; + hr = queueAsyncOperation(OpRestart); + } + return hr; + } + + HRESULT stop() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::stop"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpStop); + if (SUCCEEDED(hr)) { + m_state = State_Stopped; + hr = queueAsyncOperation(OpStop); + } + return hr; + } + + HRESULT pause() + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::pause"; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpPause); + if (SUCCEEDED(hr)) { + m_state = State_Paused; + hr = queueAsyncOperation(OpPause); + } + return hr; + } + + HRESULT setRate(float rate) + { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "MediaStream::setRate" << rate; +#endif + QMutexLocker locker(&m_mutex); + HRESULT hr = validateOperation(OpSetRate); + if (SUCCEEDED(hr)) { + m_rate = rate; + hr = queueAsyncOperation(OpSetRate); + } + return hr; + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + m_pixelFormats.clear(); + clearMediaTypes(); + if (!m_surface) + return; + const QList<QVideoFrame::PixelFormat> formats = m_surface->supportedPixelFormats(); + for (QVideoFrame::PixelFormat format : formats) { + IMFMediaType *mediaType; + if (FAILED(MFCreateMediaType(&mediaType))) { + qWarning("Failed to create mf media type!"); + continue; + } + mediaType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, TRUE); + mediaType->SetUINT32(MF_MT_COMPRESSED, FALSE); + mediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); + mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); + switch (format) { + case QVideoFrame::Format_ARGB32: + case QVideoFrame::Format_ARGB32_Premultiplied: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_ARGB32); + break; + case QVideoFrame::Format_RGB32: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); + break; + case QVideoFrame::Format_BGR24: // MFVideoFormat_RGB24 has a BGR layout + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB24); + break; + case QVideoFrame::Format_RGB565: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB565); + break; + case QVideoFrame::Format_RGB555: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB555); + break; + case QVideoFrame::Format_AYUV444: + case QVideoFrame::Format_AYUV444_Premultiplied: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_AYUV); + break; + case QVideoFrame::Format_YUV420P: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_I420); + break; + case QVideoFrame::Format_UYVY: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_UYVY); + break; + case QVideoFrame::Format_YV12: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_YV12); + break; + case QVideoFrame::Format_NV12: + mediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_NV12); + break; + default: + mediaType->Release(); + continue; + } + // QAbstractVideoSurface::supportedPixelFormats() returns formats in descending + // order of preference, while IMFMediaTypeHandler is supposed to return supported + // formats in ascending order of preference. We need to reverse the list. + m_pixelFormats.prepend(format); + m_mediaTypes.prepend(mediaType); + } + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (!m_scheduledBuffer) + return; + QVideoFrame frame = QVideoFrame( + new MediaSampleVideoBuffer(m_scheduledBuffer, m_bytesPerLine), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat()); + frame.setStartTime(m_bufferStartTime * 0.1); + frame.setEndTime((m_bufferStartTime + m_bufferDuration) * 0.1); + m_surface->present(frame); + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + if (m_rate != 0) + schedulePresentation(true); + } + + void clearScheduledFrame() + { + QMutexLocker locker(&m_mutex); + if (m_scheduledBuffer) { + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + schedulePresentation(true); + } + } + + enum + { + StartSurface = QEvent::User, + StopSurface, + FlushSurface, + PresentSurface + }; + + class PresentEvent : public QEvent + { + public: + PresentEvent(MFTIME targetTime) + : QEvent(QEvent::Type(PresentSurface)) + , m_time(targetTime) + { + } + + MFTIME targetTime() + { + return m_time; + } + + private: + MFTIME m_time; + }; + + protected: + void customEvent(QEvent *event) + { + QMutexLocker locker(&m_mutex); + if (event->type() == StartSurface) { + if (m_state == State_WaitForSurfaceStart) { + m_startResult = startSurface(); + queueAsyncOperation(OpStart); + } + } else if (event->type() == StopSurface) { + stopSurface(); + } else { + QObject::customEvent(event); + } + } + HRESULT m_startResult; + + private: + HRESULT startSurface() + { + if (!m_surface->isFormatSupported(m_surfaceFormat)) + return S_FALSE; + if (!m_surface->start(m_surfaceFormat)) + return S_FALSE; + return S_OK; + } + + void stopSurface() + { + m_surface->stop(); + } + + enum FlushState + { + DropSamples = 0, + WriteSamples + }; + + // State enum: Defines the current state of the stream. + enum State + { + State_TypeNotSet = 0, // No media type is set + State_Ready, // Media type is set, Start has never been called. + State_Prerolling, + State_Started, + State_Paused, + State_Stopped, + State_WaitForSurfaceStart, + State_Finalized, + State_Count = State_Finalized + 1 // Number of states + }; + + // StreamOperation: Defines various operations that can be performed on the stream. + enum StreamOperation + { + OpSetMediaType = 0, + OpStart, + OpPreroll, + OpRestart, + OpPause, + OpStop, + OpSetRate, + OpProcessSample, + OpPlaceMarker, + OpFinalize, + + Op_Count = OpFinalize + 1 // Number of operations + }; + + // AsyncOperation: + // Used to queue asynchronous operations. When we call MFPutWorkItem, we use this + // object for the callback state (pState). Then, when the callback is invoked, + // we can use the object to determine which asynchronous operation to perform. + class AsyncOperation : public IUnknown + { + public: + AsyncOperation(StreamOperation op) + :m_cRef(1), m_op(op) + { + } + + StreamOperation m_op; // The operation to perform. + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID iid, void** ppv) + { + if (!ppv) + return E_POINTER; + if (iid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + STDMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&m_cRef); + } + STDMETHODIMP_(ULONG) Release() + { + ULONG uCount = InterlockedDecrement(&m_cRef); + if (uCount == 0) + delete this; + // For thread safety, return a temporary variable. + return uCount; + } + + private: + long m_cRef; + virtual ~AsyncOperation() + { + Q_ASSERT(m_cRef == 0); + } + }; + + // ValidStateMatrix: Defines a look-up table that says which operations + // are valid from which states. + static BOOL ValidStateMatrix[State_Count][Op_Count]; + + long m_cRef; + QMutex m_mutex; + + IMFMediaType *m_currentMediaType; + State m_state; + IMFMediaSink *m_sink; + IMFMediaEventQueue *m_eventQueue; + DWORD m_workQueueId; + AsyncCallback<MediaStream> m_workQueueCB; + QList<IUnknown*> m_sampleQueue; + IMFAsyncResult *m_finalizeResult; // Result object for Finalize operation. + MFTIME m_startTime; // Presentation time when the clock started. + + bool m_shutdown; + QList<IMFMediaType*> m_mediaTypes; + QList<QVideoFrame::PixelFormat> m_pixelFormats; + int m_currentFormatIndex; + int m_bytesPerLine; + QVideoSurfaceFormat m_surfaceFormat; + QAbstractVideoSurface* m_surface; + MFVideoRendererControl *m_rendererControl; + + void clearMediaTypes() + { + for (IMFMediaType* mediaType : qAsConst(m_mediaTypes)) + mediaType->Release(); + m_mediaTypes.clear(); + } + + int getMediaTypeIndex(IMFMediaType *mt) + { + GUID majorType; + if (FAILED(mt->GetMajorType(&majorType))) + return -1; + if (majorType != MFMediaType_Video) + return -1; + + GUID subType; + if (FAILED(mt->GetGUID(MF_MT_SUBTYPE, &subType))) + return -1; + + for (int index = 0; index < m_mediaTypes.size(); ++index) { + GUID st; + m_mediaTypes[index]->GetGUID(MF_MT_SUBTYPE, &st); + if (st == subType) + return index; + } + return -1; + } + + int getBytesPerLine(const QVideoSurfaceFormat &format) + { + switch (format.pixelFormat()) { + // 32 bpp packed formats. + case QVideoFrame::Format_RGB32: + case QVideoFrame::Format_AYUV444: + return format.frameWidth() * 4; + // 24 bpp packed formats. + case QVideoFrame::Format_RGB24: + case QVideoFrame::Format_BGR24: + return PAD_TO_DWORD(format.frameWidth() * 3); + // 16 bpp packed formats. + case QVideoFrame::Format_RGB565: + case QVideoFrame::Format_RGB555: + case QVideoFrame::Format_YUYV: + case QVideoFrame::Format_UYVY: + return PAD_TO_DWORD(format.frameWidth() * 2); + // Planar formats. + case QVideoFrame::Format_IMC1: + case QVideoFrame::Format_IMC2: + case QVideoFrame::Format_IMC3: + case QVideoFrame::Format_IMC4: + case QVideoFrame::Format_YV12: + case QVideoFrame::Format_NV12: + case QVideoFrame::Format_YUV420P: + return PAD_TO_DWORD(format.frameWidth()); + default: + return 0; + } + } + + // Callback for MFPutWorkItem. + HRESULT onDispatchWorkItem(IMFAsyncResult* pAsyncResult) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + HRESULT hr = S_OK; + IUnknown *pState = NULL; + hr = pAsyncResult->GetState(&pState); + if (SUCCEEDED(hr)) { + // The state object is an AsncOperation object. + AsyncOperation *pOp = (AsyncOperation*)pState; + StreamOperation op = pOp->m_op; + switch (op) { + case OpStart: + endPreroll(S_FALSE); + if (m_state == State_WaitForSurfaceStart) { + hr = m_startResult; + m_state = State_Started; + } else if (!m_surface->isActive()) { + if (thread() == QThread::currentThread()) { + hr = startSurface(); + } + else { + m_state = State_WaitForSurfaceStart; + QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StartSurface), this)); + break; + } + } + + if (m_state == State_Started) + schedulePresentation(true); + case OpRestart: + endPreroll(S_FALSE); + if (SUCCEEDED(hr)) { + // Send MEStreamSinkStarted. + hr = queueEvent(MEStreamSinkStarted, GUID_NULL, hr, NULL); + // Kick things off by requesting samples... + schedulePresentation(true); + // There might be samples queue from earlier (ie, while paused). + if (SUCCEEDED(hr)) + hr = processSamplesFromQueue(WriteSamples); + } + break; + case OpPreroll: + beginPreroll(); + break; + case OpStop: + // Drop samples from queue. + hr = processSamplesFromQueue(DropSamples); + clearBufferCache(); + // Send the event even if the previous call failed. + hr = queueEvent(MEStreamSinkStopped, GUID_NULL, hr, NULL); + if (m_surface->isActive()) { + if (thread() == QThread::currentThread()) { + stopSurface(); + } + else { + QCoreApplication::postEvent(m_rendererControl, new QChildEvent(QEvent::Type(StopSurface), this)); + } + } + break; + case OpPause: + hr = queueEvent(MEStreamSinkPaused, GUID_NULL, hr, NULL); + break; + case OpSetRate: + hr = queueEvent(MEStreamSinkRateChanged, GUID_NULL, S_OK, NULL); + break; + case OpProcessSample: + case OpPlaceMarker: + hr = dispatchProcessSample(pOp); + break; + case OpFinalize: + endPreroll(S_FALSE); + hr = dispatchFinalize(pOp); + break; + } + } + + if (pState) + pState->Release(); + return hr; + } + + + HRESULT queueEvent(MediaEventType met, REFGUID guidExtendedType, HRESULT hrStatus, const PROPVARIANT* pvValue) + { + HRESULT hr = S_OK; + if (m_shutdown) + hr = MF_E_SHUTDOWN; + if (SUCCEEDED(hr)) + hr = m_eventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue); + return hr; + } + + HRESULT validateOperation(StreamOperation op) + { + Q_ASSERT(!m_shutdown); + if (ValidStateMatrix[m_state][op]) + return S_OK; + else + return MF_E_INVALIDREQUEST; + } + + HRESULT queueAsyncOperation(StreamOperation op) + { + HRESULT hr = S_OK; + AsyncOperation *asyncOp = new AsyncOperation(op); + if (asyncOp == NULL) + hr = E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(m_workQueueId, &m_workQueueCB, asyncOp); + + if (asyncOp) + asyncOp->Release(); + + return hr; + } + + HRESULT processSamplesFromQueue(FlushState bFlushData) + { + HRESULT hr = S_OK; + QList<IUnknown*>::Iterator pos = m_sampleQueue.begin(); + // Enumerate all of the samples/markers in the queue. + while (pos != m_sampleQueue.end()) { + IUnknown *pUnk = NULL; + IMarker *pMarker = NULL; + IMFSample *pSample = NULL; + pUnk = *pos; + // Figure out if this is a marker or a sample. + if (SUCCEEDED(hr)) { + hr = pUnk->QueryInterface(__uuidof(IMarker), (void**)&pMarker); + if (hr == E_NOINTERFACE) + hr = pUnk->QueryInterface(IID_IMFSample, (void**)&pSample); + } + + // Now handle the sample/marker appropriately. + if (SUCCEEDED(hr)) { + if (pMarker) { + hr = sendMarkerEvent(pMarker, bFlushData); + } else { + Q_ASSERT(pSample != NULL); // Not a marker, must be a sample + if (bFlushData == WriteSamples) + hr = processSampleData(pSample); + } + } + if (pMarker) + pMarker->Release(); + if (pSample) + pSample->Release(); + + if (FAILED(hr)) + break; + + pos++; + } + + clearSampleQueue(); + return hr; + } + + void beginPreroll() + { + if (m_prerolling) + return; + m_prerolling = true; + clearSampleQueue(); + clearBufferCache(); + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + + void endPreroll(HRESULT hrStatus) + { + if (!m_prerolling) + return; + m_prerolling = false; + queueEvent(MEStreamSinkPrerolled, GUID_NULL, hrStatus, NULL); + } + MFTIME m_prerollTargetTime; + bool m_prerolling; + + void clearSampleQueue() { + for (IUnknown* sample : qAsConst(m_sampleQueue)) + sample->Release(); + m_sampleQueue.clear(); + } + + HRESULT sendMarkerEvent(IMarker *pMarker, FlushState FlushState) + { + HRESULT hr = S_OK; + HRESULT hrStatus = S_OK; // Status code for marker event. + if (FlushState == DropSamples) + hrStatus = E_ABORT; + + PROPVARIANT var; + PropVariantInit(&var); + + // Get the context data. + hr = pMarker->GetContext(&var); + + if (SUCCEEDED(hr)) + hr = queueEvent(MEStreamSinkMarker, GUID_NULL, hrStatus, &var); + + PropVariantClear(&var); + return hr; + } + + HRESULT dispatchProcessSample(AsyncOperation* pOp) + { + HRESULT hr = S_OK; + Q_ASSERT(pOp != NULL); + Q_UNUSED(pOp); + hr = processSamplesFromQueue(WriteSamples); + // We are in the middle of an asynchronous operation, so if something failed, send an error. + if (FAILED(hr)) + hr = queueEvent(MEError, GUID_NULL, hr, NULL); + + return hr; + } + + HRESULT dispatchFinalize(AsyncOperation*) + { + HRESULT hr = S_OK; + // Write any samples left in the queue... + hr = processSamplesFromQueue(WriteSamples); + + // Set the async status and invoke the callback. + m_finalizeResult->SetStatus(hr); + hr = MFInvokeCallback(m_finalizeResult); + return hr; + } + + HRESULT processSampleData(IMFSample *pSample) + { + m_sampleRequested = false; + + LONGLONG time, duration = -1; + HRESULT hr = pSample->GetSampleTime(&time); + if (SUCCEEDED(hr)) + pSample->GetSampleDuration(&duration); + + if (m_prerolling) { + if (SUCCEEDED(hr) && ((time - m_prerollTargetTime) * m_rate) >= 0) { + IMFMediaBuffer *pBuffer = NULL; + hr = pSample->ConvertToContiguousBuffer(&pBuffer); + if (SUCCEEDED(hr)) { + SampleBuffer sb; + sb.m_buffer = pBuffer; + sb.m_time = time; + sb.m_duration = duration; + m_bufferCache.push_back(sb); + endPreroll(S_OK); + } + } else { + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + } else { + bool requestSample = true; + // If the time stamp is too early, just discard this sample. + if (SUCCEEDED(hr) && ((time - m_startTime) * m_rate) >= 0) { + IMFMediaBuffer *pBuffer = NULL; + hr = pSample->ConvertToContiguousBuffer(&pBuffer); + if (SUCCEEDED(hr)) { + SampleBuffer sb; + sb.m_buffer = pBuffer; + sb.m_time = time; + sb.m_duration = duration; + m_bufferCache.push_back(sb); + } + if (m_rate == 0) + requestSample = false; + } + schedulePresentation(requestSample); + } + return hr; + } + + class SampleBuffer + { + public: + IMFMediaBuffer *m_buffer; + LONGLONG m_time; + LONGLONG m_duration; + }; + QList<SampleBuffer> m_bufferCache; + static const int BUFFER_CACHE_SIZE = 2; + + void clearBufferCache() + { + for (SampleBuffer sb : qAsConst(m_bufferCache)) + sb.m_buffer->Release(); + m_bufferCache.clear(); + + if (m_scheduledBuffer) { + m_scheduledBuffer->Release(); + m_scheduledBuffer = NULL; + } + } + + void schedulePresentation(bool requestSample) + { + if (m_state == State_Paused || m_state == State_Prerolling) + return; + if (!m_scheduledBuffer) { + //get time from presentation time + MFTIME currentTime = m_startTime, sysTime; + bool timeOK = true; + if (m_rate != 0) { + if (FAILED(m_presentationClock->GetCorrelatedTime(0, ¤tTime, &sysTime))) + timeOK = false; + } + while (!m_bufferCache.isEmpty()) { + SampleBuffer sb = m_bufferCache.takeFirst(); + if (timeOK && ((sb.m_time - currentTime) * m_rate) < 0) { + sb.m_buffer->Release(); +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "currentPresentTime =" << float(currentTime / 10000) * 0.001f << " and sampleTime is" << float(sb.m_time / 10000) * 0.001f; +#endif + continue; + } + m_scheduledBuffer = sb.m_buffer; + m_bufferStartTime = sb.m_time; + m_bufferDuration = sb.m_duration; + QCoreApplication::postEvent(m_rendererControl, new PresentEvent(sb.m_time)); + if (m_rate == 0) + queueEvent(MEStreamSinkScrubSampleComplete, GUID_NULL, S_OK, NULL); + break; + } + } + if (requestSample && !m_sampleRequested && m_bufferCache.size() < BUFFER_CACHE_SIZE) { + m_sampleRequested = true; + queueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, NULL); + } + } + IMFMediaBuffer *m_scheduledBuffer; + MFTIME m_bufferStartTime; + MFTIME m_bufferDuration; + IMFPresentationClock *m_presentationClock; + bool m_sampleRequested; + float m_rate; + }; + + BOOL MediaStream::ValidStateMatrix[MediaStream::State_Count][MediaStream::Op_Count] = + { + // States: Operations: + // SetType Start Preroll, Restart Pause Stop SetRate Sample Marker Finalize + /* NotSet */ TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, + + /* Ready */ TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE, + + /* Prerolling */ TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + + /* Start */ FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + + /* Pause */ FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, + + /* Stop */ FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE, + + /*WaitForSurfaceStart*/ FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, + + /* Final */ FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE + + // Note about states: + // 1. OnClockRestart should only be called from paused state. + // 2. While paused, the sink accepts samples but does not process them. + }; + + class MediaSink : public IMFFinalizableMediaSink, + public IMFClockStateSink, + public IMFMediaSinkPreroll, + public IMFGetService, + public IMFRateSupport + { + public: + MediaSink(MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_shutdown(false) + , m_presentationClock(0) + , m_playRate(1) + { + m_stream = new MediaStream(this, rendererControl); + } + + ~MediaSink() + { + Q_ASSERT(m_shutdown); + } + + void setSurface(QAbstractVideoSurface *surface) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->setSurface(surface); + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->supportedFormatsChanged(); + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->present(); + } + + void clearScheduledFrame() + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return; + m_stream->clearScheduledFrame(); + } + + MFTIME getTime() + { + QMutexLocker locker(&m_mutex); + if (!m_presentationClock) + return 0; + MFTIME time, sysTime; + m_presentationClock->GetCorrelatedTime(0, &time, &sysTime); + return time; + } + + float getPlayRate() + { + QMutexLocker locker(&m_mutex); + return m_playRate; + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) + { + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFMediaSink) { + *ppvObject = static_cast<IMFMediaSink*>(this); + } else if (riid == IID_IMFGetService) { + *ppvObject = static_cast<IMFGetService*>(this); + } else if (riid == IID_IMFMediaSinkPreroll) { + *ppvObject = static_cast<IMFMediaSinkPreroll*>(this); + } else if (riid == IID_IMFClockStateSink) { + *ppvObject = static_cast<IMFClockStateSink*>(this); + } else if (riid == IID_IMFRateSupport) { + *ppvObject = static_cast<IMFRateSupport*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(static_cast<IMFFinalizableMediaSink*>(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + // IMFGetService methods + STDMETHODIMP GetService(const GUID &guidService, + const IID &riid, + LPVOID *ppvObject) + { + if (!ppvObject) + return E_POINTER; + + if (guidService != MF_RATE_CONTROL_SERVICE) + return MF_E_UNSUPPORTED_SERVICE; + + return QueryInterface(riid, ppvObject); + } + + //IMFMediaSinkPreroll + STDMETHODIMP NotifyPreroll(MFTIME hnsUpcomingStartTime) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->startPreroll(hnsUpcomingStartTime); + } + + //from IMFFinalizableMediaSink + STDMETHODIMP BeginFinalize(IMFAsyncCallback *pCallback, IUnknown *punkState) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->finalize(pCallback, punkState); + } + + STDMETHODIMP EndFinalize(IMFAsyncResult *pResult) + { + HRESULT hr = S_OK; + // Return the status code from the async result. + if (pResult == NULL) + hr = E_INVALIDARG; + else + hr = pResult->GetStatus(); + return hr; + } + + //from IMFMediaSink + STDMETHODIMP GetCharacteristics( + DWORD *pdwCharacteristics) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *pdwCharacteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_CAN_PREROLL; + return S_OK; + } + + STDMETHODIMP AddStreamSink( + DWORD, + IMFMediaType *, + IMFStreamSink **) + { + QMutexLocker locker(&m_mutex); + return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED; + } + + STDMETHODIMP RemoveStreamSink( + DWORD) + { + QMutexLocker locker(&m_mutex); + return m_shutdown ? MF_E_SHUTDOWN : MF_E_STREAMSINKS_FIXED; + } + + STDMETHODIMP GetStreamSinkCount( + DWORD *pcStreamSinkCount) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *pcStreamSinkCount = 1; + return S_OK; + } + + STDMETHODIMP GetStreamSinkByIndex( + DWORD dwIndex, + IMFStreamSink **ppStreamSink) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (dwIndex != 0) + return MF_E_INVALIDINDEX; + + *ppStreamSink = m_stream; + m_stream->AddRef(); + return S_OK; + } + + STDMETHODIMP GetStreamSinkById( + DWORD dwStreamSinkIdentifier, + IMFStreamSink **ppStreamSink) + { + if (ppStreamSink == NULL) + return E_INVALIDARG; + if (dwStreamSinkIdentifier != MediaStream::DEFAULT_MEDIA_STREAM_ID) + return MF_E_INVALIDSTREAMNUMBER; + + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + *ppStreamSink = m_stream; + m_stream->AddRef(); + return S_OK; + } + + STDMETHODIMP SetPresentationClock( + IMFPresentationClock *pPresentationClock) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + if (m_presentationClock) { + m_presentationClock->RemoveClockStateSink(this); + m_presentationClock->Release(); + } + m_presentationClock = pPresentationClock; + if (m_presentationClock) { + m_presentationClock->AddRef(); + m_presentationClock->AddClockStateSink(this); + } + m_stream->setClock(m_presentationClock); + return S_OK; + } + + STDMETHODIMP GetPresentationClock( + IMFPresentationClock **ppPresentationClock) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + *ppPresentationClock = m_presentationClock; + if (m_presentationClock) { + m_presentationClock->AddRef(); + return S_OK; + } + return MF_E_NO_CLOCK; + } + + STDMETHODIMP Shutdown(void) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + + m_stream->shutdown(); + if (m_presentationClock) { + m_presentationClock->Release(); + m_presentationClock = NULL; + } + m_stream->Release(); + m_stream = NULL; + m_shutdown = true; + return S_OK; + } + + // IMFClockStateSink methods + STDMETHODIMP OnClockStart(MFTIME, LONGLONG llClockStartOffset) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->start(llClockStartOffset); + } + + STDMETHODIMP OnClockStop(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->stop(); + } + + STDMETHODIMP OnClockPause(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->pause(); + } + + STDMETHODIMP OnClockRestart(MFTIME) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + return m_stream->restart(); + } + + STDMETHODIMP OnClockSetRate(MFTIME, float flRate) + { + QMutexLocker locker(&m_mutex); + if (m_shutdown) + return MF_E_SHUTDOWN; + m_playRate = flRate; + return m_stream->setRate(flRate); + } + + // IMFRateSupport methods + STDMETHODIMP GetFastestRate(MFRATE_DIRECTION eDirection, + BOOL fThin, + float *pflRate) + { + if (!pflRate) + return E_POINTER; + + *pflRate = (fThin ? 8.f : 2.0f) * (eDirection == MFRATE_FORWARD ? 1 : -1) ; + + return S_OK; + } + + STDMETHODIMP GetSlowestRate(MFRATE_DIRECTION eDirection, + BOOL fThin, + float *pflRate) + { + Q_UNUSED(eDirection); + Q_UNUSED(fThin); + + if (!pflRate) + return E_POINTER; + + // we support any rate + *pflRate = 0.f; + + return S_OK; + } + + STDMETHODIMP IsRateSupported(BOOL fThin, + float flRate, + float *pflNearestSupportedRate) + { + HRESULT hr = S_OK; + + if (!qFuzzyIsNull(flRate)) { + MFRATE_DIRECTION direction = flRate > 0.f ? MFRATE_FORWARD + : MFRATE_REVERSE; + + float fastestRate = 0.f; + float slowestRate = 0.f; + GetFastestRate(direction, fThin, &fastestRate); + GetSlowestRate(direction, fThin, &slowestRate); + + if (direction == MFRATE_REVERSE) + qSwap(fastestRate, slowestRate); + + if (flRate < slowestRate || flRate > fastestRate) { + hr = MF_E_UNSUPPORTED_RATE; + if (pflNearestSupportedRate) { + *pflNearestSupportedRate = qBound(slowestRate, + flRate, + fastestRate); + } + } + } else if (pflNearestSupportedRate) { + *pflNearestSupportedRate = flRate; + } + + return hr; + } + + private: + long m_cRef; + QMutex m_mutex; + bool m_shutdown; + IMFPresentationClock *m_presentationClock; + MediaStream *m_stream; + float m_playRate; + }; + + class VideoRendererActivate : public IMFActivate + { + public: + VideoRendererActivate(MFVideoRendererControl *rendererControl) + : m_cRef(1) + , m_sink(0) + , m_rendererControl(rendererControl) + , m_attributes(0) + , m_surface(0) + { + MFCreateAttributes(&m_attributes, 0); + m_sink = new MediaSink(rendererControl); + } + + ~VideoRendererActivate() + { + m_attributes->Release(); + } + + //from IUnknown + STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) + { + if (!ppvObject) + return E_POINTER; + if (riid == IID_IMFActivate) { + *ppvObject = static_cast<IMFActivate*>(this); + } else if (riid == IID_IMFAttributes) { + *ppvObject = static_cast<IMFAttributes*>(this); + } else if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(static_cast<IMFActivate*>(this)); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + STDMETHODIMP_(ULONG) AddRef(void) + { + return InterlockedIncrement(&m_cRef); + } + + STDMETHODIMP_(ULONG) Release(void) + { + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; + } + + //from IMFActivate + STDMETHODIMP ActivateObject(REFIID riid, void **ppv) + { + if (!ppv) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (!m_sink) { + m_sink = new MediaSink(m_rendererControl); + if (m_surface) + m_sink->setSurface(m_surface); + } + return m_sink->QueryInterface(riid, ppv); + } + + STDMETHODIMP ShutdownObject(void) + { + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + if (m_sink) { + hr = m_sink->Shutdown(); + m_sink->Release(); + m_sink = NULL; + } + return hr; + } + + STDMETHODIMP DetachObject(void) + { + QMutexLocker locker(&m_mutex); + if (m_sink) { + m_sink->Release(); + m_sink = NULL; + } + return S_OK; + } + + //from IMFAttributes + STDMETHODIMP GetItem( + REFGUID guidKey, + PROPVARIANT *pValue) + { + return m_attributes->GetItem(guidKey, pValue); + } + + STDMETHODIMP GetItemType( + REFGUID guidKey, + MF_ATTRIBUTE_TYPE *pType) + { + return m_attributes->GetItemType(guidKey, pType); + } + + STDMETHODIMP CompareItem( + REFGUID guidKey, + REFPROPVARIANT Value, + BOOL *pbResult) + { + return m_attributes->CompareItem(guidKey, Value, pbResult); + } + + STDMETHODIMP Compare( + IMFAttributes *pTheirs, + MF_ATTRIBUTES_MATCH_TYPE MatchType, + BOOL *pbResult) + { + return m_attributes->Compare(pTheirs, MatchType, pbResult); + } + + STDMETHODIMP GetUINT32( + REFGUID guidKey, + UINT32 *punValue) + { + return m_attributes->GetUINT32(guidKey, punValue); + } + + STDMETHODIMP GetUINT64( + REFGUID guidKey, + UINT64 *punValue) + { + return m_attributes->GetUINT64(guidKey, punValue); + } + + STDMETHODIMP GetDouble( + REFGUID guidKey, + double *pfValue) + { + return m_attributes->GetDouble(guidKey, pfValue); + } + + STDMETHODIMP GetGUID( + REFGUID guidKey, + GUID *pguidValue) + { + return m_attributes->GetGUID(guidKey, pguidValue); + } + + STDMETHODIMP GetStringLength( + REFGUID guidKey, + UINT32 *pcchLength) + { + return m_attributes->GetStringLength(guidKey, pcchLength); + } + + STDMETHODIMP GetString( + REFGUID guidKey, + LPWSTR pwszValue, + UINT32 cchBufSize, + UINT32 *pcchLength) + { + return m_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); + } + + STDMETHODIMP GetAllocatedString( + REFGUID guidKey, + LPWSTR *ppwszValue, + UINT32 *pcchLength) + { + return m_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); + } + + STDMETHODIMP GetBlobSize( + REFGUID guidKey, + UINT32 *pcbBlobSize) + { + return m_attributes->GetBlobSize(guidKey, pcbBlobSize); + } + + STDMETHODIMP GetBlob( + REFGUID guidKey, + UINT8 *pBuf, + UINT32 cbBufSize, + UINT32 *pcbBlobSize) + { + return m_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); + } + + STDMETHODIMP GetAllocatedBlob( + REFGUID guidKey, + UINT8 **ppBuf, + UINT32 *pcbSize) + { + return m_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); + } + + STDMETHODIMP GetUnknown( + REFGUID guidKey, + REFIID riid, + LPVOID *ppv) + { + return m_attributes->GetUnknown(guidKey, riid, ppv); + } + + STDMETHODIMP SetItem( + REFGUID guidKey, + REFPROPVARIANT Value) + { + return m_attributes->SetItem(guidKey, Value); + } + + STDMETHODIMP DeleteItem( + REFGUID guidKey) + { + return m_attributes->DeleteItem(guidKey); + } + + STDMETHODIMP DeleteAllItems(void) + { + return m_attributes->DeleteAllItems(); + } + + STDMETHODIMP SetUINT32( + REFGUID guidKey, + UINT32 unValue) + { + return m_attributes->SetUINT32(guidKey, unValue); + } + + STDMETHODIMP SetUINT64( + REFGUID guidKey, + UINT64 unValue) + { + return m_attributes->SetUINT64(guidKey, unValue); + } + + STDMETHODIMP SetDouble( + REFGUID guidKey, + double fValue) + { + return m_attributes->SetDouble(guidKey, fValue); + } + + STDMETHODIMP SetGUID( + REFGUID guidKey, + REFGUID guidValue) + { + return m_attributes->SetGUID(guidKey, guidValue); + } + + STDMETHODIMP SetString( + REFGUID guidKey, + LPCWSTR wszValue) + { + return m_attributes->SetString(guidKey, wszValue); + } + + STDMETHODIMP SetBlob( + REFGUID guidKey, + const UINT8 *pBuf, + UINT32 cbBufSize) + { + return m_attributes->SetBlob(guidKey, pBuf, cbBufSize); + } + + STDMETHODIMP SetUnknown( + REFGUID guidKey, + IUnknown *pUnknown) + { + return m_attributes->SetUnknown(guidKey, pUnknown); + } + + STDMETHODIMP LockStore(void) + { + return m_attributes->LockStore(); + } + + STDMETHODIMP UnlockStore(void) + { + return m_attributes->UnlockStore(); + } + + STDMETHODIMP GetCount( + UINT32 *pcItems) + { + return m_attributes->GetCount(pcItems); + } + + STDMETHODIMP GetItemByIndex( + UINT32 unIndex, + GUID *pguidKey, + PROPVARIANT *pValue) + { + return m_attributes->GetItemByIndex(unIndex, pguidKey, pValue); + } + + STDMETHODIMP CopyAllItems( + IMFAttributes *pDest) + { + return m_attributes->CopyAllItems(pDest); + } + + ///////////////////////////////// + void setSurface(QAbstractVideoSurface *surface) + { + QMutexLocker locker(&m_mutex); + if (m_surface == surface) + return; + + m_surface = surface; + + if (!m_sink) + return; + m_sink->setSurface(m_surface); + } + + void supportedFormatsChanged() + { + QMutexLocker locker(&m_mutex); + if (!m_sink) + return; + m_sink->supportedFormatsChanged(); + } + + void present() + { + QMutexLocker locker(&m_mutex); + if (!m_sink) + return; + m_sink->present(); + } + + void clearScheduledFrame() + { + QMutexLocker locker(&m_mutex); + if (!m_sink) + return; + m_sink->clearScheduledFrame(); + } + + MFTIME getTime() + { + if (m_sink) + return m_sink->getTime(); + return 0; + } + + float getPlayRate() + { + if (m_sink) + return m_sink->getPlayRate(); + return 1; + } + + private: + long m_cRef; + bool m_shutdown; + MediaSink *m_sink; + MFVideoRendererControl *m_rendererControl; + IMFAttributes *m_attributes; + QAbstractVideoSurface *m_surface; + QMutex m_mutex; + }; +} + + +class EVRCustomPresenterActivate : public MFAbstractActivate +{ +public: + EVRCustomPresenterActivate(); + ~EVRCustomPresenterActivate() + { } + + STDMETHODIMP ActivateObject(REFIID riid, void **ppv); + STDMETHODIMP ShutdownObject(); + STDMETHODIMP DetachObject(); + + void setSurface(QAbstractVideoSurface *surface); + +private: + EVRCustomPresenter *m_presenter; + QAbstractVideoSurface *m_surface; + QMutex m_mutex; +}; + + +MFVideoRendererControl::MFVideoRendererControl(QObject *parent) + : QVideoRendererControl(parent) + , m_surface(0) + , m_currentActivate(0) + , m_callback(0) + , m_presenterActivate(0) +{ +} + +MFVideoRendererControl::~MFVideoRendererControl() +{ + clear(); +} + +void MFVideoRendererControl::clear() +{ + if (m_surface) + m_surface->stop(); + + if (m_presenterActivate) { + m_presenterActivate->ShutdownObject(); + m_presenterActivate->Release(); + m_presenterActivate = NULL; + } + + if (m_currentActivate) { + m_currentActivate->ShutdownObject(); + m_currentActivate->Release(); + } + m_currentActivate = NULL; +} + +void MFVideoRendererControl::releaseActivate() +{ + clear(); +} + +QAbstractVideoSurface *MFVideoRendererControl::surface() const +{ + return m_surface; +} + +void MFVideoRendererControl::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface) + disconnect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged())); + m_surface = surface; + + if (m_surface) { + connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged())); + } + + if (m_presenterActivate) + m_presenterActivate->setSurface(m_surface); + else if (m_currentActivate) + static_cast<VideoRendererActivate*>(m_currentActivate)->setSurface(m_surface); +} + +void MFVideoRendererControl::customEvent(QEvent *event) +{ + if (m_presenterActivate) + return; + + if (!m_currentActivate) + return; + + if (event->type() == MediaStream::PresentSurface) { + MFTIME targetTime = static_cast<MediaStream::PresentEvent*>(event)->targetTime(); + MFTIME currentTime = static_cast<VideoRendererActivate*>(m_currentActivate)->getTime(); + float playRate = static_cast<VideoRendererActivate*>(m_currentActivate)->getPlayRate(); + if (!qFuzzyIsNull(playRate) && targetTime != currentTime) { + // If the scheduled frame is too late, skip it + const int interval = ((targetTime - currentTime) / 10000) / playRate; + if (interval < 0) + static_cast<VideoRendererActivate*>(m_currentActivate)->clearScheduledFrame(); + else + QTimer::singleShot(interval, this, SLOT(present())); + } else { + present(); + } + return; + } + if (event->type() >= MediaStream::StartSurface) { + QChildEvent *childEvent = static_cast<QChildEvent*>(event); + static_cast<MediaStream*>(childEvent->child())->customEvent(event); + } else { + QObject::customEvent(event); + } +} + +void MFVideoRendererControl::supportedFormatsChanged() +{ + if (m_presenterActivate) + return; + + if (m_currentActivate) + static_cast<VideoRendererActivate*>(m_currentActivate)->supportedFormatsChanged(); +} + +void MFVideoRendererControl::present() +{ + if (m_presenterActivate) + return; + + if (m_currentActivate) + static_cast<VideoRendererActivate*>(m_currentActivate)->present(); +} + +IMFActivate* MFVideoRendererControl::createActivate() +{ + Q_ASSERT(m_surface); + + clear(); + + // Create the EVR media sink, but replace the presenter with our own + if (SUCCEEDED(MFCreateVideoRendererActivate(::GetShellWindow(), &m_currentActivate))) { + m_presenterActivate = new EVRCustomPresenterActivate; + m_currentActivate->SetUnknown(MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE, m_presenterActivate); + } else { + m_currentActivate = new VideoRendererActivate(this); + } + + setSurface(m_surface); + + return m_currentActivate; +} + + +EVRCustomPresenterActivate::EVRCustomPresenterActivate() + : MFAbstractActivate() + , m_presenter(0) + , m_surface(0) +{ } + +HRESULT EVRCustomPresenterActivate::ActivateObject(REFIID riid, void **ppv) +{ + if (!ppv) + return E_INVALIDARG; + QMutexLocker locker(&m_mutex); + if (!m_presenter) { + m_presenter = new EVRCustomPresenter; + if (m_surface) + m_presenter->setSurface(m_surface); + } + return m_presenter->QueryInterface(riid, ppv); +} + +HRESULT EVRCustomPresenterActivate::ShutdownObject() +{ + // The presenter does not implement IMFShutdown so + // this function is the same as DetachObject() + return DetachObject(); +} + +HRESULT EVRCustomPresenterActivate::DetachObject() +{ + QMutexLocker locker(&m_mutex); + if (m_presenter) { + m_presenter->Release(); + m_presenter = 0; + } + return S_OK; +} + +void EVRCustomPresenterActivate::setSurface(QAbstractVideoSurface *surface) +{ + QMutexLocker locker(&m_mutex); + if (m_surface == surface) + return; + + m_surface = surface; + + if (m_presenter) + m_presenter->setSurface(surface); +} + +#include "moc_mfvideorenderercontrol_p.cpp" +#include "mfvideorenderercontrol.moc" diff --git a/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h b/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h new file mode 100644 index 000000000..dd8a6a278 --- /dev/null +++ b/src/multimedia/platform/wmf/player/mfvideorenderercontrol_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Mobility Components. +** +** $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 MFVIDEORENDERERCONTROL_H +#define MFVIDEORENDERERCONTROL_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" +#include <mfapi.h> +#include <mfidl.h> + +QT_USE_NAMESPACE + +class EVRCustomPresenterActivate; + +class MFVideoRendererControl : public QVideoRendererControl +{ + Q_OBJECT +public: + MFVideoRendererControl(QObject *parent = 0); + ~MFVideoRendererControl(); + + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + IMFActivate* createActivate(); + void releaseActivate(); + +protected: + void customEvent(QEvent *event); + +private Q_SLOTS: + void supportedFormatsChanged(); + void present(); + +private: + void clear(); + + QAbstractVideoSurface *m_surface; + IMFActivate *m_currentActivate; + IMFSampleGrabberSinkCallback *m_callback; + + EVRCustomPresenterActivate *m_presenterActivate; +}; + +#endif diff --git a/src/multimedia/platform/wmf/player/player.pri b/src/multimedia/platform/wmf/player/player.pri new file mode 100644 index 000000000..3263b84ba --- /dev/null +++ b/src/multimedia/platform/wmf/player/player.pri @@ -0,0 +1,32 @@ +INCLUDEPATH += $$PWD + +LIBS += -lgdi32 -luser32 +QMAKE_USE += wmf + +HEADERS += \ + $$PWD/mfplayerservice_p.h \ + $$PWD/mfplayersession_p.h \ + $$PWD/mfplayercontrol_p.h \ + $$PWD/mfvideorenderercontrol_p.h \ + $$PWD/mfaudioendpointcontrol_p.h \ + $$PWD/mfmetadatacontrol_p.h \ + $$PWD/mfaudioprobecontrol_p.h \ + $$PWD/mfvideoprobecontrol_p.h \ + $$PWD/mfevrvideowindowcontrol_p.h \ + $$PWD/samplegrabber_p.h \ + $$PWD/mftvideo_p.h \ + $$PWD/mfactivate_p.h + +SOURCES += \ + $$PWD/mfplayerservice.cpp \ + $$PWD/mfplayersession.cpp \ + $$PWD/mfplayercontrol.cpp \ + $$PWD/mfvideorenderercontrol.cpp \ + $$PWD/mfaudioendpointcontrol.cpp \ + $$PWD/mfmetadatacontrol.cpp \ + $$PWD/mfaudioprobecontrol.cpp \ + $$PWD/mfvideoprobecontrol.cpp \ + $$PWD/mfevrvideowindowcontrol.cpp \ + $$PWD/samplegrabber.cpp \ + $$PWD/mftvideo.cpp \ + $$PWD/mfactivate.cpp diff --git a/src/multimedia/platform/wmf/player/samplegrabber.cpp b/src/multimedia/platform/wmf/player/samplegrabber.cpp new file mode 100644 index 000000000..6c14dc152 --- /dev/null +++ b/src/multimedia/platform/wmf/player/samplegrabber.cpp @@ -0,0 +1,173 @@ +/**************************************************************************** +** +** 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 "samplegrabber_p.h" +#include "mfaudioprobecontrol_p.h" + +STDMETHODIMP SampleGrabberCallback::QueryInterface(REFIID riid, void** ppv) +{ + if (!ppv) + return E_POINTER; + if (riid == IID_IMFSampleGrabberSinkCallback) { + *ppv = static_cast<IMFSampleGrabberSinkCallback*>(this); + } else if (riid == IID_IMFClockStateSink) { + *ppv = static_cast<IMFClockStateSink*>(this); + } else if (riid == IID_IUnknown) { + *ppv = static_cast<IUnknown*>(this); + } else { + *ppv = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) SampleGrabberCallback::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) SampleGrabberCallback::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) { + delete this; + } + return cRef; + +} + +// IMFClockStateSink methods. + +STDMETHODIMP SampleGrabberCallback::OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) +{ + Q_UNUSED(hnsSystemTime); + Q_UNUSED(llClockStartOffset); + return S_OK; +} + +STDMETHODIMP SampleGrabberCallback::OnClockStop(MFTIME hnsSystemTime) +{ + Q_UNUSED(hnsSystemTime); + return S_OK; +} + +STDMETHODIMP SampleGrabberCallback::OnClockPause(MFTIME hnsSystemTime) +{ + Q_UNUSED(hnsSystemTime); + return S_OK; +} + +STDMETHODIMP SampleGrabberCallback::OnClockRestart(MFTIME hnsSystemTime) +{ + Q_UNUSED(hnsSystemTime); + return S_OK; +} + +STDMETHODIMP SampleGrabberCallback::OnClockSetRate(MFTIME hnsSystemTime, float flRate) +{ + Q_UNUSED(hnsSystemTime); + Q_UNUSED(flRate); + return S_OK; +} + +// IMFSampleGrabberSink methods. + +STDMETHODIMP SampleGrabberCallback::OnSetPresentationClock(IMFPresentationClock* pClock) +{ + Q_UNUSED(pClock); + return S_OK; +} + +STDMETHODIMP SampleGrabberCallback::OnShutdown() +{ + return S_OK; +} + +void AudioSampleGrabberCallback::addProbe(MFAudioProbeControl* probe) +{ + QMutexLocker locker(&m_audioProbeMutex); + + if (m_audioProbes.contains(probe)) + return; + + m_audioProbes.append(probe); +} + +void AudioSampleGrabberCallback::removeProbe(MFAudioProbeControl* probe) +{ + QMutexLocker locker(&m_audioProbeMutex); + m_audioProbes.removeOne(probe); +} + +void AudioSampleGrabberCallback::setFormat(const QAudioFormat& format) +{ + m_format = format; +} + +STDMETHODIMP AudioSampleGrabberCallback::OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, + LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer, + DWORD dwSampleSize) +{ + Q_UNUSED(dwSampleFlags); + Q_UNUSED(llSampleTime); + Q_UNUSED(llSampleDuration); + + if (guidMajorMediaType != GUID_NULL && guidMajorMediaType != MFMediaType_Audio) + return S_OK; + + QMutexLocker locker(&m_audioProbeMutex); + + if (m_audioProbes.isEmpty()) + return S_OK; + + // Check if sample has a presentation time + if (llSampleTime == _I64_MAX) { + // Set default QAudioBuffer start time + llSampleTime = -1; + } else { + // WMF uses 100-nanosecond units, Qt uses microseconds + llSampleTime /= 10; + } + + for (MFAudioProbeControl* probe : qAsConst(m_audioProbes)) + probe->bufferProbed((const char*)pSampleBuffer, dwSampleSize, m_format, llSampleTime); + + return S_OK; +} diff --git a/src/multimedia/platform/wmf/player/samplegrabber_p.h b/src/multimedia/platform/wmf/player/samplegrabber_p.h new file mode 100644 index 000000000..74eb62065 --- /dev/null +++ b/src/multimedia/platform/wmf/player/samplegrabber_p.h @@ -0,0 +1,107 @@ +/**************************************************************************** +** +** 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 SAMPLEGRABBER_H +#define SAMPLEGRABBER_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/qmutex.h> +#include <QtCore/qlist.h> +#include <QtMultimedia/qaudioformat.h> +#include <mfapi.h> +#include <mfidl.h> + +class MFAudioProbeControl; + +class SampleGrabberCallback : public IMFSampleGrabberSinkCallback +{ +public: + // IUnknown methods + STDMETHODIMP QueryInterface(REFIID iid, void** ppv); + STDMETHODIMP_(ULONG) AddRef(); + STDMETHODIMP_(ULONG) Release(); + + // IMFClockStateSink methods + STDMETHODIMP OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset); + STDMETHODIMP OnClockStop(MFTIME hnsSystemTime); + STDMETHODIMP OnClockPause(MFTIME hnsSystemTime); + STDMETHODIMP OnClockRestart(MFTIME hnsSystemTime); + STDMETHODIMP OnClockSetRate(MFTIME hnsSystemTime, float flRate); + + // IMFSampleGrabberSinkCallback methods + STDMETHODIMP OnSetPresentationClock(IMFPresentationClock* pClock); + STDMETHODIMP OnShutdown(); + +protected: + SampleGrabberCallback() : m_cRef(1) {} + +public: + virtual ~SampleGrabberCallback() {} + +private: + long m_cRef; +}; + +class AudioSampleGrabberCallback: public SampleGrabberCallback { +public: + void addProbe(MFAudioProbeControl* probe); + void removeProbe(MFAudioProbeControl* probe); + void setFormat(const QAudioFormat& format); + + STDMETHODIMP OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags, + LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE * pSampleBuffer, + DWORD dwSampleSize); + +private: + QList<MFAudioProbeControl*> m_audioProbes; + QMutex m_audioProbeMutex; + QAudioFormat m_format; +}; + +#endif // SAMPLEGRABBER_H diff --git a/src/multimedia/platform/wmf/sourceresolver.cpp b/src/multimedia/platform/wmf/sourceresolver.cpp new file mode 100644 index 000000000..93af15a74 --- /dev/null +++ b/src/multimedia/platform/wmf/sourceresolver.cpp @@ -0,0 +1,325 @@ +/**************************************************************************** +** +** 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 "mfstream_p.h" +#include "sourceresolver_p.h" +#include <mferror.h> +#include <nserror.h> +#include <QtCore/qfile.h> +#include <QtCore/qdebug.h> +#include <QtMultimedia/qmediaplayer.h> + +/* + SourceResolver is separated from MFPlayerSession to handle the work of resolving a media source + asynchronously. You call SourceResolver::load to request resolving a media source asynchronously, + and it will emit mediaSourceReady() when resolving is done. You can call SourceResolver::cancel to + stop the previous load operation if there is any. +*/ + +SourceResolver::SourceResolver() + : m_cRef(1) + , m_cancelCookie(0) + , m_sourceResolver(0) + , m_mediaSource(0) + , m_stream(0) +{ +} + +SourceResolver::~SourceResolver() +{ + shutdown(); + if (m_mediaSource) { + m_mediaSource->Release(); + m_mediaSource = NULL; + } + + if (m_cancelCookie) + m_cancelCookie->Release(); + if (m_sourceResolver) + m_sourceResolver->Release(); +} + +STDMETHODIMP SourceResolver::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else if (riid == IID_IMFAsyncCallback) { + *ppvObject = static_cast<IMFAsyncCallback*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) SourceResolver::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) SourceResolver::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + this->deleteLater(); + return cRef; +} + +HRESULT STDMETHODCALLTYPE SourceResolver::Invoke(IMFAsyncResult *pAsyncResult) +{ + QMutexLocker locker(&m_mutex); + + if (!m_sourceResolver) + return S_OK; + + MF_OBJECT_TYPE ObjectType = MF_OBJECT_INVALID; + IUnknown* pSource = NULL; + State *state = static_cast<State*>(pAsyncResult->GetStateNoAddRef()); + + HRESULT hr = S_OK; + if (state->fromStream()) + hr = m_sourceResolver->EndCreateObjectFromByteStream(pAsyncResult, &ObjectType, &pSource); + else + hr = m_sourceResolver->EndCreateObjectFromURL(pAsyncResult, &ObjectType, &pSource); + + if (state->sourceResolver() != m_sourceResolver) { + //This is a cancelled one + return S_OK; + } + + if (m_cancelCookie) { + m_cancelCookie->Release(); + m_cancelCookie = NULL; + } + + if (FAILED(hr)) { + emit error(hr); + return S_OK; + } + + if (m_mediaSource) { + m_mediaSource->Release(); + m_mediaSource = NULL; + } + + hr = pSource->QueryInterface(IID_PPV_ARGS(&m_mediaSource)); + pSource->Release(); + if (FAILED(hr)) { + emit error(hr); + return S_OK; + } + + emit mediaSourceReady(); + + return S_OK; +} + +HRESULT STDMETHODCALLTYPE SourceResolver::GetParameters(DWORD*, DWORD*) +{ + return E_NOTIMPL; +} + +void SourceResolver::load(const QUrl &url, QIODevice* stream) +{ + QMutexLocker locker(&m_mutex); + HRESULT hr = S_OK; + if (!m_sourceResolver) + hr = MFCreateSourceResolver(&m_sourceResolver); + + if (m_stream) { + m_stream->Release(); + m_stream = NULL; + } + + if (FAILED(hr)) { + qWarning() << "Failed to create Source Resolver!"; + emit error(hr); + } else if (stream) { + QString urlString = url.toString(); + m_stream = new MFStream(stream, false); + hr = m_sourceResolver->BeginCreateObjectFromByteStream( + m_stream, urlString.isEmpty() ? 0 : reinterpret_cast<LPCWSTR>(urlString.utf16()), + MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE + , NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { +#ifdef DEBUG_MEDIAFOUNDATION + qDebug() << "loading :" << url; + qDebug() << "url path =" << url.path().mid(1); +#endif +#ifdef TEST_STREAMING + //Testing stream function + if (url.scheme() == QLatin1String("file")) { + stream = new QFile(url.path().mid(1)); + if (stream->open(QIODevice::ReadOnly)) { + m_stream = new MFStream(stream, true); + hr = m_sourceResolver->BeginCreateObjectFromByteStream( + m_stream, reinterpret_cast<const OLECHAR *>(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE, + NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { + delete stream; + emit error(QMediaPlayer::FormatError); + } + } else +#endif + if (url.scheme() == QLatin1String("qrc")) { + // If the canonical URL refers to a Qt resource, open with QFile and use + // the stream playback capability to play. + stream = new QFile(QLatin1Char(':') + url.path()); + if (stream->open(QIODevice::ReadOnly)) { + m_stream = new MFStream(stream, true); + hr = m_sourceResolver->BeginCreateObjectFromByteStream( + m_stream, reinterpret_cast<const OLECHAR *>(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE, + NULL, &m_cancelCookie, this, new State(m_sourceResolver, true)); + if (FAILED(hr)) { + qWarning() << "Unsupported stream!"; + emit error(hr); + } + } else { + delete stream; + emit error(QMediaPlayer::FormatError); + } + } else { + hr = m_sourceResolver->BeginCreateObjectFromURL( + reinterpret_cast<const OLECHAR *>(url.toString().utf16()), + MF_RESOLUTION_MEDIASOURCE | MF_RESOLUTION_CONTENT_DOES_NOT_HAVE_TO_MATCH_EXTENSION_OR_MIME_TYPE, + NULL, &m_cancelCookie, this, new State(m_sourceResolver, false)); + if (FAILED(hr)) { + qWarning() << "Unsupported url scheme!"; + emit error(hr); + } + } + } +} + +void SourceResolver::cancel() +{ + QMutexLocker locker(&m_mutex); + if (m_cancelCookie) { + m_sourceResolver->CancelObjectCreation(m_cancelCookie); + m_cancelCookie->Release(); + m_cancelCookie = NULL; + m_sourceResolver->Release(); + m_sourceResolver = NULL; + } +} + +void SourceResolver::shutdown() +{ + if (m_mediaSource) { + m_mediaSource->Shutdown(); + m_mediaSource->Release(); + m_mediaSource = NULL; + } + + if (m_stream) { + m_stream->Release(); + m_stream = NULL; + } +} + +IMFMediaSource* SourceResolver::mediaSource() const +{ + return m_mediaSource; +} + +///////////////////////////////////////////////////////////////////////////////// +SourceResolver::State::State(IMFSourceResolver *sourceResolver, bool fromStream) + : m_cRef(0) + , m_sourceResolver(sourceResolver) + , m_fromStream(fromStream) +{ + sourceResolver->AddRef(); +} + +SourceResolver::State::~State() +{ + m_sourceResolver->Release(); +} + +STDMETHODIMP SourceResolver::State::QueryInterface(REFIID riid, LPVOID *ppvObject) +{ + if (!ppvObject) + return E_POINTER; + if (riid == IID_IUnknown) { + *ppvObject = static_cast<IUnknown*>(this); + } else { + *ppvObject = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP_(ULONG) SourceResolver::State::AddRef(void) +{ + return InterlockedIncrement(&m_cRef); +} + +STDMETHODIMP_(ULONG) SourceResolver::State::Release(void) +{ + LONG cRef = InterlockedDecrement(&m_cRef); + if (cRef == 0) + delete this; + // For thread safety, return a temporary variable. + return cRef; +} + +IMFSourceResolver* SourceResolver::State::sourceResolver() const +{ + return m_sourceResolver; +} + +bool SourceResolver::State::fromStream() const +{ + return m_fromStream; +} + diff --git a/src/multimedia/platform/wmf/sourceresolver_p.h b/src/multimedia/platform/wmf/sourceresolver_p.h new file mode 100644 index 000000000..07da1d8bd --- /dev/null +++ b/src/multimedia/platform/wmf/sourceresolver_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef SOURCERESOLVER_H +#define SOURCERESOLVER_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 "mfstream_p.h" +#include <QUrl> + +class SourceResolver: public QObject, public IMFAsyncCallback +{ + Q_OBJECT +public: + SourceResolver(); + + ~SourceResolver(); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + STDMETHODIMP_(ULONG) AddRef(void); + STDMETHODIMP_(ULONG) Release(void); + + HRESULT STDMETHODCALLTYPE Invoke(IMFAsyncResult *pAsyncResult); + + HRESULT STDMETHODCALLTYPE GetParameters(DWORD*, DWORD*); + + void load(const QUrl &url, QIODevice* stream); + + void cancel(); + + void shutdown(); + + IMFMediaSource* mediaSource() const; + +Q_SIGNALS: + void error(long hr); + void mediaSourceReady(); + +private: + class State : public IUnknown + { + public: + State(IMFSourceResolver *sourceResolver, bool fromStream); + ~State(); + + STDMETHODIMP QueryInterface(REFIID riid, LPVOID *ppvObject); + + STDMETHODIMP_(ULONG) AddRef(void); + + STDMETHODIMP_(ULONG) Release(void); + + IMFSourceResolver* sourceResolver() const; + bool fromStream() const; + + private: + long m_cRef; + IMFSourceResolver *m_sourceResolver; + bool m_fromStream; + }; + + long m_cRef; + IUnknown *m_cancelCookie; + IMFSourceResolver *m_sourceResolver; + IMFMediaSource *m_mediaSource; + MFStream *m_stream; + QMutex m_mutex; +}; + +#endif diff --git a/src/multimedia/platform/wmf/wmf.pri b/src/multimedia/platform/wmf/wmf.pri new file mode 100644 index 000000000..81d63c3bf --- /dev/null +++ b/src/multimedia/platform/wmf/wmf.pri @@ -0,0 +1,21 @@ +QT += network + +win32:!qtHaveModule(opengl) { + LIBS_PRIVATE += -lgdi32 -luser32 +} + +INCLUDEPATH += . + +HEADERS += \ + $$PWD/wmfserviceplugin_p.h \ + $$PWD/mfstream_p.h \ + $$PWD/sourceresolver_p.h + +SOURCES += \ + $$PWD/wmfserviceplugin.cpp \ + $$PWD/mfstream.cpp \ + $$PWD/sourceresolver.cpp + +include (evr/evr.pri) +include (player/player.pri) +include (decoder/decoder.pri) diff --git a/src/multimedia/platform/wmf/wmfserviceplugin.cpp b/src/multimedia/platform/wmf/wmfserviceplugin.cpp new file mode 100644 index 000000000..0eb20e482 --- /dev/null +++ b/src/multimedia/platform/wmf/wmfserviceplugin.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** 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 <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtCore/QFile> + +#include "wmfserviceplugin_p.h" +#include "mfplayerservice_p.h" + +#include <mfapi.h> + +namespace +{ +static int g_refCount = 0; +void addRefCount() +{ + g_refCount++; + if (g_refCount == 1) { + CoInitialize(NULL); + MFStartup(MF_VERSION); + } +} + +void releaseRefCount() +{ + g_refCount--; + if (g_refCount == 0) { + MFShutdown(); + CoUninitialize(); + } +} + +} + +QMediaService* WMFServicePlugin::create(QString const& key) +{ + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) { + addRefCount(); + return new MFPlayerService; + } + + //qDebug() << "unsupported key:" << key; + return 0; +} + +void WMFServicePlugin::release(QMediaService *service) +{ + delete service; + releaseRefCount(); +} + +QByteArray WMFServicePlugin::defaultDevice(const QByteArray &) const +{ + return QByteArray(); +} + +QList<QByteArray> WMFServicePlugin::devices(const QByteArray &) const +{ + return QList<QByteArray>(); +} + +QString WMFServicePlugin::deviceDescription(const QByteArray &, const QByteArray &) +{ + return QString(); +} + diff --git a/src/multimedia/platform/wmf/wmfserviceplugin_p.h b/src/multimedia/platform/wmf/wmfserviceplugin_p.h new file mode 100644 index 000000000..ef769b22a --- /dev/null +++ b/src/multimedia/platform/wmf/wmfserviceplugin_p.h @@ -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$ +** +****************************************************************************/ + +#ifndef WMFSERVICEPLUGIN_H +#define WMFSERVICEPLUGIN_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 <QtMultimedia/private/qtmultimediaglobal_p.h> +#include "qmediaserviceproviderplugin.h" + +QT_USE_NAMESPACE + +class WMFServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedDevicesInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "wmf.json") + +public: + QMediaService* create(QString const& key); + void release(QMediaService *service); + + QByteArray defaultDevice(const QByteArray &service) const; + QList<QByteArray> devices(const QByteArray &service) const; + QString deviceDescription(const QByteArray &service, const QByteArray &device); +}; + +#endif // WMFSERVICEPLUGIN_H |