From da77331952f38992fbd4a650a02ad975a4efaa36 Mon Sep 17 00:00:00 2001 From: Niels Weber Date: Mon, 5 May 2014 16:16:05 +0200 Subject: [PATCH 01/48] Improve Magnify and Ripple effects on video shader example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-enable Magnify shader. Use correct coordinates for Magnify and Ripple shader. Task-number: QTBUG-38121 Change-Id: Ie8d962ba841d074c9ebcf3b86f948848ad6a1812 Reviewed-by: Topi Reiniö --- .../video/qmlvideofx/qml/qmlvideofx/EffectMagnify.qml | 4 +++- .../video/qmlvideofx/qml/qmlvideofx/EffectRipple.qml | 4 +++- .../video/qmlvideofx/qml/qmlvideofx/EffectSelectionList.qml | 2 +- examples/multimedia/video/qmlvideofx/shaders/magnify.fsh | 3 +++ examples/multimedia/video/qmlvideofx/shaders/ripple.fsh | 3 ++- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectMagnify.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectMagnify.qml index 3fd35eaa..84ac4ce7 100644 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectMagnify.qml +++ b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectMagnify.qml @@ -39,7 +39,8 @@ ** ****************************************************************************/ -import QtQuick 2.0 +import QtQuick 2.1 +import QtQuick.Window 2.1 Effect { id: root @@ -57,6 +58,7 @@ Effect { property real posX: -1 property real posY: -1 + property real pixDens: Screen.pixelDensity QtObject { id: d diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectRipple.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectRipple.qml index e0a2b022..d481fdfd 100644 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectRipple.qml +++ b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectRipple.qml @@ -39,7 +39,8 @@ ** ****************************************************************************/ -import QtQuick 2.0 +import QtQuick 2.1 +import QtQuick.Window 2.1 Effect { parameters: ListModel { @@ -56,6 +57,7 @@ Effect { // Transform slider values, and bind result to shader uniforms property real amplitude: parameters.get(0).value * 0.03 property real n: parameters.get(1).value * 7 + property real pixDens: Screen.pixelDensity property real time: 0 NumberAnimation on time { loops: Animation.Infinite; from: 0; to: Math.PI * 2; duration: 600 } diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectSelectionList.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectSelectionList.qml index fa92bb7c..da9a9614 100644 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectSelectionList.qml +++ b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/EffectSelectionList.qml @@ -51,7 +51,7 @@ ListModel { ListElement { name: "Emboss"; source: "EffectEmboss.qml" } ListElement { name: "Glow"; source: "EffectGlow.qml" } ListElement { name: "Isolate"; source: "EffectIsolate.qml" } - //ListElement { name: "Magnify"; source: "EffectMagnify.qml" } + ListElement { name: "Magnify"; source: "EffectMagnify.qml" } ListElement { name: "Page curl"; source: "EffectPageCurl.qml" } ListElement { name: "Pixelate"; source: "EffectPixelate.qml" } ListElement { name: "Posterize"; source: "EffectPosterize.qml" } diff --git a/examples/multimedia/video/qmlvideofx/shaders/magnify.fsh b/examples/multimedia/video/qmlvideofx/shaders/magnify.fsh index 0387d25d..fb7e2a04 100644 --- a/examples/multimedia/video/qmlvideofx/shaders/magnify.fsh +++ b/examples/multimedia/video/qmlvideofx/shaders/magnify.fsh @@ -50,12 +50,15 @@ uniform float targetWidth; uniform float targetHeight; uniform float posX; uniform float posY; +uniform float pixDens; void main() { vec2 tc = qt_TexCoord0; vec2 center = vec2(posX, posY); vec2 xy = gl_FragCoord.xy - center.xy; + xy.x -= (pixDens * 14.0); + xy.y -= (pixDens * 29.0); float r = sqrt(xy.x * xy.x + xy.y * xy.y); if (r < radius) { float h = diffractionIndex * 0.5 * radius; diff --git a/examples/multimedia/video/qmlvideofx/shaders/ripple.fsh b/examples/multimedia/video/qmlvideofx/shaders/ripple.fsh index b70f36d9..428c041c 100644 --- a/examples/multimedia/video/qmlvideofx/shaders/ripple.fsh +++ b/examples/multimedia/video/qmlvideofx/shaders/ripple.fsh @@ -55,12 +55,13 @@ const int ITER = 7; const float RATE = 0.1; uniform float amplitude; uniform float n; +uniform float pixDens; void main() { vec2 uv = qt_TexCoord0.xy; vec2 tc = uv; - vec2 p = vec2(-1.0 + 2.0 * gl_FragCoord.x / targetWidth, -(-1.0 + 2.0 * gl_FragCoord.y / targetHeight)); + vec2 p = vec2(-1.0 + 2.0 * (gl_FragCoord.x - (pixDens * 14.0)) / targetWidth, -(-1.0 + 2.0 * (gl_FragCoord.y - (pixDens * 29.0)) / targetHeight)); float diffx = 0.0; float diffy = 0.0; vec4 col; From ff527de0133d597293459cc7d0f03f6203995cc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lisandro=20Dami=C3=A1n=20Nicanor=20P=C3=A9rez=20Meyer?= Date: Thu, 10 Jul 2014 19:19:37 -0300 Subject: [PATCH 02/48] Detect V4L availability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Do not build related stuff if not found. Makes GStreamer support available on Hurd. Task-number: QTBUG-39762 Change-Id: I1f70b6975e5bef99ab2441aac4d90508bc8b64bd Reviewed-by: Lisandro Damián Nicanor Pérez Meyer Reviewed-by: Yoann Lopes --- config.tests/linux_v4l/linux_v4l.pro | 1 + config.tests/linux_v4l/main.cpp | 47 +++++++++++++++++++ qtmultimedia.pro | 1 + src/gsttools/gsttools.pro | 2 + .../qgstreamervideoinputdevicecontrol.cpp | 5 ++ src/plugins/gstreamer/camerabin/camerabin.pro | 2 + .../camerabin/camerabinserviceplugin.cpp | 5 ++ .../gstreamer/mediacapture/mediacapture.pro | 21 +++++---- .../mediacapture/qgstreamercaptureservice.cpp | 9 +++- .../mediacapture/qgstreamercaptureservice.h | 2 + .../qgstreamercaptureserviceplugin.cpp | 3 ++ src/plugins/plugins.pro | 4 +- 12 files changed, 91 insertions(+), 11 deletions(-) create mode 100644 config.tests/linux_v4l/linux_v4l.pro create mode 100644 config.tests/linux_v4l/main.cpp diff --git a/config.tests/linux_v4l/linux_v4l.pro b/config.tests/linux_v4l/linux_v4l.pro new file mode 100644 index 00000000..28dcadcb --- /dev/null +++ b/config.tests/linux_v4l/linux_v4l.pro @@ -0,0 +1 @@ +SOURCES += main.cpp diff --git a/config.tests/linux_v4l/main.cpp b/config.tests/linux_v4l/main.cpp new file mode 100644 index 00000000..0a3040be --- /dev/null +++ b/config.tests/linux_v4l/main.cpp @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +int main(int argc, char** argv) +{ + return 0; +} diff --git a/qtmultimedia.pro b/qtmultimedia.pro index c7f093cc..bec5925b 100644 --- a/qtmultimedia.pro +++ b/qtmultimedia.pro @@ -25,6 +25,7 @@ win32 { qtCompileTest(gstreamer_photography) qtCompileTest(gstreamer_encodingprofiles) qtCompileTest(gstreamer_appsrc) + qtCompileTest(linux_v4l) } qtCompileTest(resourcepolicy) qtCompileTest(gpu_vivante) diff --git a/src/gsttools/gsttools.pro b/src/gsttools/gsttools.pro index 15edd04d..7c809a77 100644 --- a/src/gsttools/gsttools.pro +++ b/src/gsttools/gsttools.pro @@ -100,6 +100,8 @@ config_gstreamer_appsrc { LIBS_PRIVATE += -lgstapp-0.10 } +config_linux_v4l: DEFINES += USE_V4L + HEADERS += $$PRIVATE_HEADERS DESTDIR = $$QT.multimedia.libs diff --git a/src/gsttools/qgstreamervideoinputdevicecontrol.cpp b/src/gsttools/qgstreamervideoinputdevicecontrol.cpp index e4e202ca..dc008712 100644 --- a/src/gsttools/qgstreamervideoinputdevicecontrol.cpp +++ b/src/gsttools/qgstreamervideoinputdevicecontrol.cpp @@ -45,7 +45,10 @@ #include #include + +#if defined(USE_V4L) #include +#endif QGstreamerVideoInputDeviceControl::QGstreamerVideoInputDeviceControl(QObject *parent) :QVideoDeviceSelectorControl(parent), m_source(0), m_selectedDevice(0) @@ -118,6 +121,7 @@ void QGstreamerVideoInputDeviceControl::update() return; } +#if defined(USE_V4L) QDir devDir("/dev"); devDir.setFilter(QDir::System); @@ -158,4 +162,5 @@ void QGstreamerVideoInputDeviceControl::update() } qt_safe_close(fd); } +#endif } diff --git a/src/plugins/gstreamer/camerabin/camerabin.pro b/src/plugins/gstreamer/camerabin/camerabin.pro index 9efa0812..9ed821cb 100644 --- a/src/plugins/gstreamer/camerabin/camerabin.pro +++ b/src/plugins/gstreamer/camerabin/camerabin.pro @@ -81,6 +81,8 @@ config_gstreamer_photography { DEFINES += GST_USE_UNSTABLE_API #prevents warnings because of unstable photography API } +config_linux_v4l: DEFINES += USE_V4L + OTHER_FILES += \ camerabin.json diff --git a/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp b/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp index 3decd607..5fb419aa 100644 --- a/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp @@ -51,7 +51,10 @@ #include #include + +#if defined(USE_V4L) #include +#endif QT_BEGIN_NAMESPACE @@ -132,6 +135,7 @@ void CameraBinServicePlugin::updateDevices() const m_cameraDevices.clear(); m_cameraDescriptions.clear(); +#if defined(USE_V4L) QDir devDir("/dev"); devDir.setFilter(QDir::System); @@ -173,6 +177,7 @@ void CameraBinServicePlugin::updateDevices() const if (!m_cameraDevices.isEmpty()) m_defaultCameraDevice = m_cameraDevices.first(); +#endif } QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/mediacapture/mediacapture.pro b/src/plugins/gstreamer/mediacapture/mediacapture.pro index e8d039f8..5baa0fd8 100644 --- a/src/plugins/gstreamer/mediacapture/mediacapture.pro +++ b/src/plugins/gstreamer/mediacapture/mediacapture.pro @@ -15,7 +15,6 @@ HEADERS += $$PWD/qgstreamercaptureservice.h \ $$PWD/qgstreamerrecordercontrol.h \ $$PWD/qgstreamermediacontainercontrol.h \ $$PWD/qgstreamercameracontrol.h \ - $$PWD/qgstreamerv4l2input.h \ $$PWD/qgstreamercapturemetadatacontrol.h \ $$PWD/qgstreamerimagecapturecontrol.h \ $$PWD/qgstreamerimageencode.h \ @@ -28,7 +27,6 @@ SOURCES += $$PWD/qgstreamercaptureservice.cpp \ $$PWD/qgstreamerrecordercontrol.cpp \ $$PWD/qgstreamermediacontainercontrol.cpp \ $$PWD/qgstreamercameracontrol.cpp \ - $$PWD/qgstreamerv4l2input.cpp \ $$PWD/qgstreamercapturemetadatacontrol.cpp \ $$PWD/qgstreamerimagecapturecontrol.cpp \ $$PWD/qgstreamerimageencode.cpp \ @@ -37,13 +35,18 @@ SOURCES += $$PWD/qgstreamercaptureservice.cpp \ # Camera usage with gstreamer needs to have #CONFIG += use_gstreamer_camera -use_gstreamer_camera { -DEFINES += USE_GSTREAMER_CAMERA +use_gstreamer_camera:config_linux_v4l { + DEFINES += USE_GSTREAMER_CAMERA + + OTHER_FILES += \ + mediacapturecamera.json + + HEADERS += \ + $$PWD/qgstreamerv4l2input.h + SOURCES += \ + $$PWD/qgstreamerv4l2input.cpp -OTHER_FILES += \ - mediacapturecamera.json } else { -OTHER_FILES += \ - mediacapture.json + OTHER_FILES += \ + mediacapture.json } - diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp index 92b362fb..2278f926 100644 --- a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp +++ b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp @@ -48,9 +48,12 @@ #include "qgstreamerimageencode.h" #include "qgstreamercameracontrol.h" #include -#include "qgstreamerv4l2input.h" #include "qgstreamercapturemetadatacontrol.h" +#if defined(USE_GSTREAMER_CAMERA) +#include "qgstreamerv4l2input.h" +#endif + #include "qgstreamerimagecapturecontrol.h" #include #include @@ -74,7 +77,9 @@ QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObje m_cameraControl = 0; m_metaDataControl = 0; +#if defined(USE_GSTREAMER_CAMERA) m_videoInput = 0; +#endif m_audioInputSelector = 0; m_videoInputDevice = 0; @@ -90,6 +95,7 @@ QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObje 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); @@ -111,6 +117,7 @@ QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObje #endif m_imageCaptureControl = new QGstreamerImageCaptureControl(m_captureSession); } +#endif m_audioInputSelector = new QGstreamerAudioInputSelector(this); connect(m_audioInputSelector, SIGNAL(activeInputChanged(QString)), m_captureSession, SLOT(setCaptureDevice(QString))); diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h index fc29b4f3..563c48c2 100644 --- a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h +++ b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h @@ -78,7 +78,9 @@ private: QGstreamerCaptureSession *m_captureSession; QGstreamerCameraControl *m_cameraControl; +#if defined(USE_GSTREAMER_CAMERA) QGstreamerV4L2Input *m_videoInput; +#endif QGstreamerCaptureMetaDataControl *m_metaDataControl; QAudioInputSelectorControl *m_audioInputSelector; diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp index 8b88fbb7..77a6c365 100644 --- a/src/plugins/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp +++ b/src/plugins/gstreamer/mediacapture/qgstreamercaptureserviceplugin.cpp @@ -52,7 +52,10 @@ #include #include + +#if defined(USE_GSTREAMER_CAMERA) #include +#endif QMediaService* QGstreamerCaptureServicePlugin::create(const QString &key) { diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 2677e269..6a23fd2a 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -43,7 +43,9 @@ unix:!mac:!android { } # v4l is turned off because it is not supported in Qt 5 - # !maemo*:SUBDIRS += v4l + # config_linux_v4l { + # !maemo*:SUBDIRS += v4l + # } } mac:!simulator { From c93c1d1dc30884b2c13bffdbc701efb5401b58b7 Mon Sep 17 00:00:00 2001 From: Dyami Caliri Date: Tue, 22 Jul 2014 11:19:14 -0700 Subject: [PATCH 03/48] CoreAudioOutput use timeout when waiting for render thread On Snow Leopard (at least), changing the default audio device while audio is playing can cause CoreAudioOutput to freeze in audioThreadStop(). It seems that the OS stops calling renderCallback when the device changes, so audioThreadStop() waits forever. Change-Id: If7244cc50f12295ff91a979ef50e3bee1273affd Reviewed-by: Andy Nichols --- src/plugins/coreaudio/coreaudiooutput.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/coreaudio/coreaudiooutput.mm b/src/plugins/coreaudio/coreaudiooutput.mm index e5e1c65e..812d9dfe 100644 --- a/src/plugins/coreaudio/coreaudiooutput.mm +++ b/src/plugins/coreaudio/coreaudiooutput.mm @@ -698,14 +698,14 @@ void CoreAudioOutput::audioThreadStop() { stopTimers(); if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) - m_threadFinished.wait(&m_mutex); + m_threadFinished.wait(&m_mutex, 500); } void CoreAudioOutput::audioThreadDrain() { stopTimers(); if (m_audioThreadState.testAndSetAcquire(Running, Draining)) - m_threadFinished.wait(&m_mutex); + m_threadFinished.wait(&m_mutex, 500); } void CoreAudioOutput::audioDeviceStop() From 5195520a5a87ef8b5afdb980a808a5a95dad4e67 Mon Sep 17 00:00:00 2001 From: "Daniele E. Domenichelli" Date: Tue, 12 Aug 2014 15:07:23 +0200 Subject: [PATCH 04/48] Fix QSGVideoNode rendering of rgb frames with padding. Change-Id: I6870cfa51b01b648494e2068be06e52b67403739 Reviewed-by: Andrew den Exter Reviewed-by: Yoann Lopes --- src/qtmultimediaquicktools/qsgvideonode_rgb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qtmultimediaquicktools/qsgvideonode_rgb.cpp b/src/qtmultimediaquicktools/qsgvideonode_rgb.cpp index ad01a08a..1316be92 100644 --- a/src/qtmultimediaquicktools/qsgvideonode_rgb.cpp +++ b/src/qtmultimediaquicktools/qsgvideonode_rgb.cpp @@ -220,7 +220,7 @@ public: stride /= 4; } - m_width = qreal(m_frame.width() / stride); + m_width = qreal(m_frame.width()) / stride; textureSize.setWidth(stride); if (m_textureSize != textureSize) { From 18e665b5a611823de13159bbaacd2134e6727180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Thu, 14 Aug 2014 15:37:28 +0200 Subject: [PATCH 05/48] Android: Remove api level test in qtmultimedia.pro ANDROID_API_VERSION env. var should take precedence, if set. Change-Id: I54325852ede27ff5c1bb19b81d3d649605607de9 Reviewed-by: Yoann Lopes --- qtmultimedia.pro | 4 ---- 1 file changed, 4 deletions(-) diff --git a/qtmultimedia.pro b/qtmultimedia.pro index bec5925b..3cec526e 100644 --- a/qtmultimedia.pro +++ b/qtmultimedia.pro @@ -12,10 +12,6 @@ win32 { qtCompileTest(evr) } else:mac { qtCompileTest(avfoundation) -} else:android:!android-no-sdk { - SDK_ROOT = $$(ANDROID_SDK_ROOT) - isEmpty(SDK_ROOT): SDK_ROOT = $$DEFAULT_ANDROID_SDK_ROOT - !exists($$SDK_ROOT/platforms/android-11/android.jar): error("QtMultimedia for Android requires API level 11") } else:qnx { qtCompileTest(mmrenderer) } else { From fb35f025e320ef1639adf9a5d7bee73faa44e442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Tue, 12 Aug 2014 16:46:50 +0200 Subject: [PATCH 06/48] OpenSL: Fix QAudioOutput::setNotifyInterval(). It was not possible to change the notify interval after calling start(). Task-number: QTBUG-40208 Change-Id: I82a626003e3bdfe7b7fc88b2f97da492c788877e Reviewed-by: Yoann Lopes --- src/plugins/opensles/qopenslesaudiooutput.cpp | 36 ++++++++++++++++--- src/plugins/opensles/qopenslesaudiooutput.h | 1 + 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/plugins/opensles/qopenslesaudiooutput.cpp b/src/plugins/opensles/qopenslesaudiooutput.cpp index 49bea0b3..9c62852d 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.cpp +++ b/src/plugins/opensles/qopenslesaudiooutput.cpp @@ -78,7 +78,8 @@ QOpenSLESAudioOutput::QOpenSLESAudioOutput(const QByteArray &device) m_periodSize(0), m_elapsedTime(0), m_processedBytes(0), - m_availableBuffers(BUFFER_COUNT) + m_availableBuffers(BUFFER_COUNT), + m_eventMask(SL_PLAYEVENT_HEADATEND) { #ifndef ANDROID m_streamType = -1; @@ -198,7 +199,33 @@ int QOpenSLESAudioOutput::bufferSize() const void QOpenSLESAudioOutput::setNotifyInterval(int ms) { - m_notifyInterval = ms > 0 ? ms : 0; + const int newInterval = ms > 0 ? ms : 0; + + if (newInterval == m_notifyInterval) + return; + + const SLuint32 newEvenMask = newInterval == 0 ? m_eventMask & ~SL_PLAYEVENT_HEADATNEWPOS + : m_eventMask & SL_PLAYEVENT_HEADATNEWPOS; + + if (m_state == QAudio::StoppedState) { + m_eventMask = newEvenMask; + m_notifyInterval = newInterval; + return; + } + + if (newEvenMask != m_eventMask + && SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, newEvenMask)) { + return; + } + + m_eventMask = newEvenMask; + + if (newInterval && SL_RESULT_SUCCESS != (*m_playItf)->SetPositionUpdatePeriod(m_playItf, + newInterval)) { + return; + } + + m_notifyInterval = newInterval; } int QOpenSLESAudioOutput::notifyInterval() const @@ -488,13 +515,12 @@ bool QOpenSLESAudioOutput::preparePlayer() return false; } - SLuint32 mask = SL_PLAYEVENT_HEADATEND; if (m_notifyInterval && SL_RESULT_SUCCESS == (*m_playItf)->SetPositionUpdatePeriod(m_playItf, m_notifyInterval)) { - mask |= SL_PLAYEVENT_HEADATNEWPOS; + m_eventMask |= SL_PLAYEVENT_HEADATNEWPOS; } - if (SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, mask)) { + if (SL_RESULT_SUCCESS != (*m_playItf)->SetCallbackEventsMask(m_playItf, m_eventMask)) { setError(QAudio::FatalError); return false; } diff --git a/src/plugins/opensles/qopenslesaudiooutput.h b/src/plugins/opensles/qopenslesaudiooutput.h index b0f01fa2..16cbc50d 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.h +++ b/src/plugins/opensles/qopenslesaudiooutput.h @@ -120,6 +120,7 @@ private: qint64 m_elapsedTime; qint64 m_processedBytes; QAtomicInt m_availableBuffers; + SLuint32 m_eventMask; qint32 m_streamType; QTime m_clockStamp; From 341b86c63fbe9e9f284e2d6547cb639f487a2ec4 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 17 Jul 2014 18:41:44 +0200 Subject: [PATCH 07/48] Android: fix retrieving metadata from assets, qrc and remote files. We need the same logic as for the media player: local files and assets must be loaded with a FileDescriptor. Because of a bug in Android API level >= 14, remote files have to be loaded in different ways depending on the version. Task-number: QTBUG-40274 Change-Id: I6411b959064d22219cf981a4dc8f4f26cf16f65f Reviewed-by: Christian Stromme --- .../qandroidmediaplayercontrol.cpp | 4 +- .../mediaplayer/qandroidmediaplayercontrol.h | 1 + .../src/mediaplayer/qandroidmediaservice.cpp | 4 +- .../qandroidmetadatareadercontrol.cpp | 10 +- .../qandroidmetadatareadercontrol.h | 4 +- .../jni/androidmediametadataretriever.cpp | 133 +++++++++++++----- .../jni/androidmediametadataretriever.h | 4 +- 7 files changed, 113 insertions(+), 47 deletions(-) diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp index 6817d65b..90efcc50 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp @@ -325,8 +325,10 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, mMediaPlayer->setDataSource(mediaPath); mMediaPlayer->prepareAsync(); - if (!reloading) + if (!reloading) { Q_EMIT mediaChanged(mMediaContent); + Q_EMIT actualMediaLocationChanged(mediaPath); + } resetBufferingProgress(); } diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h index 5744c11b..1f61809c 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h @@ -80,6 +80,7 @@ public: Q_SIGNALS: void metaDataUpdated(); + void actualMediaLocationChanged(const QString &url); public Q_SLOTS: void setPosition(qint64 position) Q_DECL_OVERRIDE; diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaservice.cpp b/src/plugins/android/src/mediaplayer/qandroidmediaservice.cpp index 17595867..c6a7d3c3 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaservice.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmediaservice.cpp @@ -53,8 +53,8 @@ QAndroidMediaService::QAndroidMediaService(QObject *parent) { mMediaControl = new QAndroidMediaPlayerControl; mMetadataControl = new QAndroidMetaDataReaderControl; - connect(mMediaControl, SIGNAL(mediaChanged(QMediaContent)), - mMetadataControl, SLOT(onMediaChanged(QMediaContent))); + connect(mMediaControl, SIGNAL(actualMediaLocationChanged(QString)), + mMetadataControl, SLOT(onMediaChanged(QString))); connect(mMediaControl, SIGNAL(metaDataUpdated()), mMetadataControl, SLOT(onUpdateMetaData())); } diff --git a/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.cpp b/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.cpp index 82bd7499..7f68bc13 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.cpp @@ -101,18 +101,18 @@ QStringList QAndroidMetaDataReaderControl::availableMetaData() const return m_metadata.keys(); } -void QAndroidMetaDataReaderControl::onMediaChanged(const QMediaContent &media) +void QAndroidMetaDataReaderControl::onMediaChanged(const QString &url) { if (!m_retriever) return; - m_mediaContent = media; + m_mediaLocation = url; updateData(); } void QAndroidMetaDataReaderControl::onUpdateMetaData() { - if (!m_retriever || m_mediaContent.isNull()) + if (!m_retriever || m_mediaLocation.isEmpty()) return; updateData(); @@ -122,8 +122,8 @@ void QAndroidMetaDataReaderControl::updateData() { m_metadata.clear(); - if (!m_mediaContent.isNull()) { - if (m_retriever->setDataSource(m_mediaContent.canonicalUrl())) { + if (!m_mediaLocation.isEmpty()) { + if (m_retriever->setDataSource(m_mediaLocation)) { QString mimeType = m_retriever->extractMetadata(AndroidMediaMetadataRetriever::MimeType); if (!mimeType.isNull()) m_metadata.insert(QMediaMetaData::MediaType, mimeType); diff --git a/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.h b/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.h index 67b92f1e..a8f1d92f 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.h +++ b/src/plugins/android/src/mediaplayer/qandroidmetadatareadercontrol.h @@ -62,13 +62,13 @@ public: QStringList availableMetaData() const Q_DECL_OVERRIDE; public Q_SLOTS: - void onMediaChanged(const QMediaContent &media); + void onMediaChanged(const QString &url); void onUpdateMetaData(); private: void updateData(); - QMediaContent m_mediaContent; + QString m_mediaLocation; bool m_available; QVariantMap m_metadata; diff --git a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp index 7dfc6a6e..83f12cb8 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp +++ b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.cpp @@ -43,9 +43,24 @@ #include #include +#include +#include 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"); @@ -76,55 +91,105 @@ void AndroidMediaMetadataRetriever::release() m_metadataRetriever.callMethod("release"); } -bool AndroidMediaMetadataRetriever::setDataSource(const QUrl &url) +bool AndroidMediaMetadataRetriever::setDataSource(const QString &urlString) { if (!m_metadataRetriever.isValid()) return false; QJNIEnvironmentPrivate env; + QUrl url(urlString); - bool loaded = false; + if (url.isLocalFile()) { // also includes qrc files (copied to a temp file) + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.path()); + QJNIObjectPrivate fileInputStream("java/io/FileInputStream", + "(Ljava/lang/String;)V", + string.object()); - QJNIObjectPrivate string = QJNIObjectPrivate::fromString(url.toString()); + if (exceptionCheckAndClear(env)) + return false; - QJNIObjectPrivate uri = m_metadataRetriever.callStaticObjectMethod("android/net/Uri", - "parse", - "(Ljava/lang/String;)Landroid/net/Uri;", - string.object()); - if (env->ExceptionCheck()) { - env->ExceptionClear(); + QJNIObjectPrivate fd = fileInputStream.callObjectMethod("getFD", + "()Ljava/io/FileDescriptor;"); + if (exceptionCheckAndClear(env)) { + fileInputStream.callMethod("close"); + exceptionCheckAndClear(env); + return false; + } + + m_metadataRetriever.callMethod("setDataSource", + "(Ljava/io/FileDescriptor;)V", + fd.object()); + + bool ok = !exceptionCheckAndClear(env); + + fileInputStream.callMethod("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("close"); + exceptionCheckAndClear(env); + return false; + } + + m_metadataRetriever.callMethod("setDataSource", + "(Ljava/io/FileDescriptor;JJ)V", + fd.object(), + assetFd.callMethod("getStartOffset"), + assetFd.callMethod("getLength")); + + bool ok = !exceptionCheckAndClear(env); + + assetFd.callMethod("close"); + exceptionCheckAndClear(env); + + if (!ok) + return false; + } else if (QtAndroidPrivate::androidSdkVersion() >= 14) { + // On API levels >= 14, only setDataSource(String, Map) accepts remote media + QJNIObjectPrivate string = QJNIObjectPrivate::fromString(urlString); + QJNIObjectPrivate hash("java/util/HashMap"); + + m_metadataRetriever.callMethod("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(urlString); + 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("setDataSource", "(Landroid/content/Context;Landroid/net/Uri;)V", QtAndroidPrivate::activity(), uri.object()); - if (env->ExceptionCheck()) - env->ExceptionClear(); - else - loaded = true; + if (exceptionCheckAndClear(env)) + return false; } - return loaded; -} - -bool AndroidMediaMetadataRetriever::setDataSource(const QString &path) -{ - if (!m_metadataRetriever.isValid()) - return false; - - QJNIEnvironmentPrivate env; - - bool loaded = false; - - m_metadataRetriever.callMethod("setDataSource", - "(Ljava/lang/String;)V", - QJNIObjectPrivate::fromString(path).object()); - if (env->ExceptionCheck()) - env->ExceptionClear(); - else - loaded = true; - - return loaded; + return true; } QT_END_NAMESPACE diff --git a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h index f18cec11..1a4a876e 100644 --- a/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h +++ b/src/plugins/android/src/wrappers/jni/androidmediametadataretriever.h @@ -43,7 +43,6 @@ #define ANDROIDMEDIAMETADATARETRIEVER_H #include -#include QT_BEGIN_NAMESPACE @@ -81,8 +80,7 @@ public: QString extractMetadata(MetadataKey key); void release(); - bool setDataSource(const QUrl &url); - bool setDataSource(const QString &path); + bool setDataSource(const QString &url); private: QJNIObjectPrivate m_metadataRetriever; From bee6244e2428c4a2f4b62dd1e8896f310b2208c8 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 8 May 2014 15:22:11 +0200 Subject: [PATCH 08/48] AVFoundation: fix retrieving tracks information from live sources. For live sources, tracks information is available only after the AVPlayer changed its status to AVPlayerStatusReadyToPlay. It also seems to be available only from AVPlayerItem.tracks rather than AVAsset.tracks. The audioAvailableChanged() and videoAvailableChanged() signals are now correclty emitted and the video layer is correctly positioned for live sources. Task-number: QTBUG-38666 Change-Id: I8ee015a6ce81694c1fc1e44c679887cf7ccb0fd6 Reviewed-by: Andy Nichols --- .../mediaplayer/avfmediaplayersession.h | 3 + .../mediaplayer/avfmediaplayersession.mm | 103 +++++++++--------- 2 files changed, 55 insertions(+), 51 deletions(-) diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h index 58a2d84c..18e923ae 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h @@ -156,6 +156,9 @@ private: QByteArray rawData; }; + void setAudioAvailable(bool available); + void setVideoAvailable(bool available); + AVFMediaPlayerService *m_service; AVFVideoOutput *m_videoOutput; diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm index a73974cc..106e81a3 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm @@ -70,15 +70,11 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe AVPlayerItem *m_playerItem; AVPlayerLayer *m_playerLayer; NSURL *m_URL; - bool m_audioAvailable; - bool m_videoAvailable; } @property (readonly, getter=player) AVPlayer* m_player; @property (readonly, getter=playerItem) AVPlayerItem* m_playerItem; @property (readonly, getter=playerLayer) AVPlayerLayer* m_playerLayer; -@property (readonly, getter=audioAvailable) bool m_audioAvailable; -@property (readonly, getter=videoAvailable) bool m_videoAvailable; @property (readonly, getter=session) AVFMediaPlayerSession* m_session; - (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session; @@ -96,7 +92,7 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe @implementation AVFMediaPlayerSessionObserver -@synthesize m_player, m_playerItem, m_playerLayer, m_audioAvailable, m_videoAvailable, m_session; +@synthesize m_player, m_playerItem, m_playerLayer, m_session; - (AVFMediaPlayerSessionObserver *) initWithMediaPlayerSession:(AVFMediaPlayerSession *)session { @@ -186,18 +182,6 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe return; } - m_audioAvailable = false; - m_videoAvailable = false; - - //Check each track of asset for audio and video content - NSArray *tracks = [asset tracks]; - for (AVAssetTrack *track in tracks) { - if ([track hasMediaCharacteristic:AVMediaCharacteristicAudible]) - m_audioAvailable = true; - if ([track hasMediaCharacteristic:AVMediaCharacteristicVisual]) - m_videoAvailable = true; - } - //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) @@ -258,18 +242,7 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe m_playerLayer = [AVPlayerLayer playerLayerWithPlayer:m_player]; [m_playerLayer retain]; m_playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; - - //Get the native size of the new item, and reset the bounds of the player layer - AVAsset *asset = m_playerItem.asset; - if (asset) { - NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; - if ([tracks count]) { - AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; - m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); - m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); - } - } - + m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); } //Observe the AVPlayer "currentItem" property to find out when any @@ -366,22 +339,8 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe { AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey]; if (m_playerItem != newPlayerItem) - { m_playerItem = newPlayerItem; - //Get the native size of the new item, and reset the bounds of the player layer - //AVAsset *asset = m_playerItem.asset; - AVAsset *asset = [m_playerItem asset]; - if (asset) { - NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; - if ([tracks count]) { - AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; - m_playerLayer.anchorPoint = CGPointMake(0.0f, 0.0f); - m_playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); - } - } - - } if (self.session) QMetaObject::invokeMethod(m_session, "processCurrentItemChanged", Qt::AutoConnection); } @@ -513,6 +472,9 @@ void AVFMediaPlayerSession::setMedia(const QMediaContent &content, QIODevice *st m_resources = content; m_mediaStream = stream; + setAudioAvailable(false); + setVideoAvailable(false); + QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus; if (content.isNull() || content.canonicalUrl().isEmpty()) { @@ -582,14 +544,32 @@ 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 [(AVFMediaPlayerSessionObserver*)m_observer audioAvailable]; + 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 [(AVFMediaPlayerSessionObserver*)m_observer videoAvailable]; + return m_videoAvailable; } bool AVFMediaPlayerSession::isSeekable() const @@ -802,16 +782,37 @@ void AVFMediaPlayerSession::processLoadStateChange() bool isPlaying = (m_state != QMediaPlayer::StoppedState); if (currentStatus == AVPlayerStatusReadyToPlay) { + AVPlayerItem *playerItem = [(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; + } + } + } + + // Get the native size of the video, and reset the bounds of the player layer + AVPlayerLayer *playerLayer = [(AVFMediaPlayerSessionObserver*)m_observer playerLayer]; + if (videoTrack && playerLayer) { + playerLayer.bounds = CGRectMake(0.0f, 0.0f, + videoTrack.naturalSize.width, + videoTrack.naturalSize.height); + } + } + qint64 currentDuration = duration(); if (m_duration != currentDuration) Q_EMIT durationChanged(m_duration = currentDuration); - if (m_audioAvailable != isAudioAvailable()) - Q_EMIT audioAvailableChanged(m_audioAvailable = !m_audioAvailable); - - if (m_videoAvailable != isVideoAvailable()) - Q_EMIT videoAvailableChanged(m_videoAvailable = !m_videoAvailable); - newStatus = isPlaying ? QMediaPlayer::BufferedMedia : QMediaPlayer::LoadedMedia; if (m_state == QMediaPlayer::PlayingState && [(AVFMediaPlayerSessionObserver*)m_observer player]) { From 5f0f81bcc1b82ea3418e4e5ce939ce22b11af0af Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 5 Jun 2014 15:25:55 +0200 Subject: [PATCH 09/48] AVFoundation: fix some controls not being correctly destroyed on iOS. This patch also makes sure AVF video layers are removed from their parent layer when their corresponding Qt video outputs are destroyed. Task-number: QTBUG-39385 Change-Id: I164cd0da7084f84c0473ed3e396e734acce2a22e Reviewed-by: Andy Nichols --- src/plugins/avfoundation/camera/avfcameraservice.mm | 4 ++-- .../avfoundation/mediaplayer/avfmediaplayerservice.mm | 5 +++-- src/plugins/avfoundation/mediaplayer/avfvideowidget.mm | 4 +++- .../avfoundation/mediaplayer/avfvideowindowcontrol.mm | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/plugins/avfoundation/camera/avfcameraservice.mm b/src/plugins/avfoundation/camera/avfcameraservice.mm index 25111c5c..966202ed 100644 --- a/src/plugins/avfoundation/camera/avfcameraservice.mm +++ b/src/plugins/avfoundation/camera/avfcameraservice.mm @@ -135,9 +135,9 @@ QMediaControl *AVFCameraService::requestControl(const char *name) void AVFCameraService::releaseControl(QMediaControl *control) { if (m_videoOutput == control) { - m_videoOutput = 0; m_session->setVideoOutput(0); - delete control; + delete m_videoOutput; + m_videoOutput = 0; } } diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayerservice.mm b/src/plugins/avfoundation/mediaplayer/avfmediaplayerservice.mm index e5549803..bb75adb8 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayerservice.mm +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayerservice.mm @@ -118,14 +118,15 @@ void AVFMediaPlayerService::releaseControl(QMediaControl *control) #ifdef QT_DEBUG_AVF qDebug() << Q_FUNC_INFO << control; #endif -#if defined(Q_OS_OSX) if (m_videoOutput == control) { +#if defined(Q_OS_OSX) AVFVideoRendererControl *renderControl = qobject_cast(m_videoOutput); if (renderControl) renderControl->setSurface(0); +#endif m_videoOutput = 0; m_session->setVideoOutput(0); + delete control; } -#endif } diff --git a/src/plugins/avfoundation/mediaplayer/avfvideowidget.mm b/src/plugins/avfoundation/mediaplayer/avfvideowidget.mm index d4fa7c4c..2893921f 100644 --- a/src/plugins/avfoundation/mediaplayer/avfvideowidget.mm +++ b/src/plugins/avfoundation/mediaplayer/avfvideowidget.mm @@ -64,8 +64,10 @@ AVFVideoWidget::~AVFVideoWidget() qDebug() << Q_FUNC_INFO; #endif - if (m_playerLayer) + if (m_playerLayer) { + [m_playerLayer removeFromSuperlayer]; [m_playerLayer release]; + } } QSize AVFVideoWidget::sizeHint() const diff --git a/src/plugins/avfoundation/mediaplayer/avfvideowindowcontrol.mm b/src/plugins/avfoundation/mediaplayer/avfvideowindowcontrol.mm index 17fc94de..8e96d732 100644 --- a/src/plugins/avfoundation/mediaplayer/avfvideowindowcontrol.mm +++ b/src/plugins/avfoundation/mediaplayer/avfvideowindowcontrol.mm @@ -61,8 +61,10 @@ AVFVideoWindowControl::AVFVideoWindowControl(QObject *parent) AVFVideoWindowControl::~AVFVideoWindowControl() { - if (m_playerLayer) + if (m_playerLayer) { + [m_playerLayer removeFromSuperlayer]; [m_playerLayer release]; + } } WId AVFVideoWindowControl::winId() const From 20da381608c61930e2eea46fe0175a355eac9a73 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Mon, 14 Jul 2014 16:44:49 +0200 Subject: [PATCH 10/48] WMF: fix bufferStatus() and availablePlaybackRanges(). - Correctly initialize and clear PROPVARIANT structures - Return coherent data even when the information is not available Change-Id: I22b46f95f255cbb740a154c6296a5c3a91e64f67 Reviewed-by: Christian Stromme --- src/plugins/wmf/player/mfplayersession.cpp | 41 ++++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp index f61f7aba..09c5a8c5 100644 --- a/src/plugins/wmf/player/mfplayersession.cpp +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -1403,14 +1403,17 @@ 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; - if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { + // 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); } - PropVariantClear(&var); #ifdef DEBUG_MEDIAFOUNDATION qDebug() << "bufferStatus: progress = " << progress; @@ -1421,22 +1424,30 @@ int MFPlayerSession::bufferStatus() QMediaTimeRange MFPlayerSession::availablePlaybackRanges() { - if (!m_netsourceStatistics) - return QMediaTimeRange(); + // defaults to the whole media + qint64 start = 0; + qint64 end = qint64(m_duration / 10000); - qint64 start = 0, end = 0; - PROPVARIANT var; - PROPERTYKEY key; - key.fmtid = MFNETSOURCE_STATISTICS; - key.pid = MFNETSOURCE_SEEKRANGESTART_ID; - if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { - start = qint64(var.uhVal.QuadPart / 10000); - key.pid = MFNETSOURCE_SEEKRANGEEND_ID; - if (SUCCEEDED(m_netsourceStatistics->GetValue(key, &var))) { - end = qint64(var.uhVal.QuadPart / 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); + } } } - PropVariantClear(&var); + return QMediaTimeRange(start, end); } From b3c2dca466042cf362ffb8d803bf05c9b8a0f95f Mon Sep 17 00:00:00 2001 From: Bjoern Breitmeyer Date: Fri, 22 Aug 2014 14:59:11 +0200 Subject: [PATCH 11/48] Restore QWindowsAudio support on wince. Enabled Audio playback with wave device on WindowsCE again. Change-Id: Ic7749821ef8f991a909cbeb29083219ea988f5dc Reviewed-by: Yoann Lopes --- config.tests/wmp/main.cpp | 5 +++- config.tests/wmp/wmp.pro | 3 +- .../windowsaudio/qwindowsaudiodeviceinfo.cpp | 30 +++++++++++++++++++ src/plugins/windowsaudio/windowsaudio.pro | 3 +- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/config.tests/wmp/main.cpp b/config.tests/wmp/main.cpp index 1667ebc8..50f4bf6c 100644 --- a/config.tests/wmp/main.cpp +++ b/config.tests/wmp/main.cpp @@ -38,8 +38,11 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ - +#ifndef _WIN32_WCE #include +#else +#include +#endif int main(int, char**) { diff --git a/config.tests/wmp/wmp.pro b/config.tests/wmp/wmp.pro index b16509cc..563de145 100644 --- a/config.tests/wmp/wmp.pro +++ b/config.tests/wmp/wmp.pro @@ -3,4 +3,5 @@ CONFIG += console SOURCES += main.cpp -LIBS += -lstrmiids -lole32 -lOleaut32 -luser32 -lgdi32 +LIBS += -lstrmiids -lole32 -lOleaut32 +!wince*:LIBS += -luser32 -lgdi32 diff --git a/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp index d37056a5..98c161ae 100644 --- a/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp +++ b/src/plugins/windowsaudio/qwindowsaudiodeviceinfo.cpp @@ -412,6 +412,7 @@ QList QWindowsAudioDeviceInfo::availableDevices(QAudio::Mode mode) Q_UNUSED(mode) QList devices; +#ifndef Q_OS_WINCE //enumerate device fullnames through directshow api CoInitialize(NULL); ICreateDevEnum *pDevEnum = NULL; @@ -463,6 +464,35 @@ QList QWindowsAudioDeviceInfo::availableDevices(QAudio::Mode mode) } } CoUninitialize(); +#else // Q_OS_WINCE + if (mode == QAudio::AudioOutput) { + WAVEOUTCAPS woc; + unsigned long iNumDevs,i; + iNumDevs = waveOutGetNumDevs(); + for (i=0;i Date: Tue, 26 Aug 2014 13:53:55 +0200 Subject: [PATCH 12/48] Added 5.3.2 change file. Change-Id: I865ae833267c6e91bf61e15acf1acb4292e5c3b2 Reviewed-by: Jani Heikkinen Reviewed-by: Christian Stromme --- dist/changes-5.3.2 | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 dist/changes-5.3.2 diff --git a/dist/changes-5.3.2 b/dist/changes-5.3.2 new file mode 100644 index 00000000..b427bb80 --- /dev/null +++ b/dist/changes-5.3.2 @@ -0,0 +1,65 @@ +Qt 5.3.2 is a bug-fix release. It maintains both forward and backward +compatibility (source and binary) with Qt 5.3.0 and 5.3.1. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + + http://qt-project.org/doc/qt-5.3 + +The Qt version 5.3 series is binary compatible with the 5.2.x series. +Applications compiled for 5.2 will continue to run with 5.3. + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + + http://bugreports.qt-project.org/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* Platform Specific Changes * +**************************************************************************** + +Android +------- + + - Fixed regression causing videos recorded with the camera not to be registered with the Android + media scanner, making them invisible to media browsing apps. + - Fixed crash when unloading a QCamera while a recording is active. + - [QTBUG-39307] Setting camera parameters on the QML Camera type (e.g. digitalZoom, flashMode) + now works correctly when set before the camera is loaded. + - [QTBUG-40208] QAudioOutput::setNotifyInterval() can now be used when the output is active. + - [QTBUG-40274] Fixed metadata not being loaded by the MediaPlayer when playing a remote media, + from a qrc file or from assets. + +iOS +--- + + - [QTBUG-39036] Audio played using SoundEffect or QAudioOutput is now correctly muted when the + device is set to silent mode or when the screen is locked. + - [QTBUG-39385] The last video frame displayed in a QML VideoOutput doesn't remain on screen + anymore after destroying the VideoOutput. + +Linux +----- + + - MediaPlayer's loops property now works correctly when playing a media from a qrc file. + - [QTBUG-29742] Fixed Qt apps hanging when audio APIs are used and PulseAudio is not running. + - [QTBUG-39949] Fixed QMediaRecorder::setOutputLocation() not working with QUrl::fromLocalFile(). + +OS X +---- + + - Application doesn't freeze anymore when changing the system audio output device while audio + is being played with QSoundEffect or QAudioOutput. + - [QTBUG-38666] Video frames are now correctly positioned on screen when playing a live stream + in a QVideoWidget. This also applies to iOS. + - [QTBUG-38668] Fixed crash when setting QMediaRecorder's output location to a URL containing + nonstandard characters. + +Windows +------- + + - The DirectShow camera backend has been almost entirely rewritten. It doesn't provide any new + feature but it now works as it should. From b6e9d52b0bcb263751542efe7f7e7379e0a7ee9f Mon Sep 17 00:00:00 2001 From: Frederik Gladhorn Date: Fri, 29 Aug 2014 11:33:37 +0200 Subject: [PATCH 13/48] Bump version Change-Id: I123f7fd8e2f88b36a69d3d5713f3b6390db610bc --- .qmake.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.qmake.conf b/.qmake.conf index 60effa7d..8c368751 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,4 +1,4 @@ load(qt_build_config) CONFIG += qt_example_installs -MODULE_VERSION = 5.3.2 +MODULE_VERSION = 5.3.3 From 0c3438c9a12fbc607eada8f938cf0ad8fdea374d Mon Sep 17 00:00:00 2001 From: Andrew Knight Date: Fri, 29 Aug 2014 16:59:09 +0300 Subject: [PATCH 14/48] winrt: Add camera service This adds a basic camera service with viewfinder (video renderer based), still image capture, and device selection support. Runtime apps must set the "webcam" and "microphone" device capabilities in order to access the hardware. This can be done by adding the following to the .pro file: WINRT_MANIFEST.capabilites_device += webcam microphone [ChangeLog] Enabled basic camera support in the winrt backend. Change-Id: If4f963ef645d93c757ae23aec9a9c8aae122324f Reviewed-by: Yoann Lopes --- src/plugins/winrt/qwinrtcameracontrol.cpp | 783 ++++++++++++++++++ src/plugins/winrt/qwinrtcameracontrol.h | 111 +++ .../winrt/qwinrtcameraimagecapturecontrol.cpp | 280 +++++++ .../winrt/qwinrtcameraimagecapturecontrol.h | 86 ++ src/plugins/winrt/qwinrtcamerainfocontrol.cpp | 60 ++ src/plugins/winrt/qwinrtcamerainfocontrol.h | 61 ++ src/plugins/winrt/qwinrtcameraservice.cpp | 104 +++ src/plugins/winrt/qwinrtcameraservice.h | 66 ++ .../qwinrtcameravideorenderercontrol.cpp | 204 +++++ .../winrt/qwinrtcameravideorenderercontrol.h | 74 ++ src/plugins/winrt/qwinrtserviceplugin.cpp | 39 + src/plugins/winrt/qwinrtserviceplugin.h | 14 + .../qwinrtvideodeviceselectorcontrol.cpp | 383 +++++++++ .../winrt/qwinrtvideodeviceselectorcontrol.h | 94 +++ src/plugins/winrt/winrt.json | 2 +- src/plugins/winrt/winrt.pro | 18 +- 16 files changed, 2375 insertions(+), 4 deletions(-) create mode 100644 src/plugins/winrt/qwinrtcameracontrol.cpp create mode 100644 src/plugins/winrt/qwinrtcameracontrol.h create mode 100644 src/plugins/winrt/qwinrtcameraimagecapturecontrol.cpp create mode 100644 src/plugins/winrt/qwinrtcameraimagecapturecontrol.h create mode 100644 src/plugins/winrt/qwinrtcamerainfocontrol.cpp create mode 100644 src/plugins/winrt/qwinrtcamerainfocontrol.h create mode 100644 src/plugins/winrt/qwinrtcameraservice.cpp create mode 100644 src/plugins/winrt/qwinrtcameraservice.h create mode 100644 src/plugins/winrt/qwinrtcameravideorenderercontrol.cpp create mode 100644 src/plugins/winrt/qwinrtcameravideorenderercontrol.h create mode 100644 src/plugins/winrt/qwinrtvideodeviceselectorcontrol.cpp create mode 100644 src/plugins/winrt/qwinrtvideodeviceselectorcontrol.h diff --git a/src/plugins/winrt/qwinrtcameracontrol.cpp b/src/plugins/winrt/qwinrtcameracontrol.cpp new file mode 100644 index 00000000..619e9731 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameracontrol.cpp @@ -0,0 +1,783 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtcameracontrol.h" +#include "qwinrtcameravideorenderercontrol.h" +#include "qwinrtvideodeviceselectorcontrol.h" +#include "qwinrtcameraimagecapturecontrol.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Media; +using namespace ABI::Windows::Media::Capture; +using namespace ABI::Windows::Media::Devices; +using namespace ABI::Windows::Media::MediaProperties; +using namespace ABI::Windows::Storage::Streams; + +QT_USE_NAMESPACE + +#define RETURN_VOID_AND_EMIT_ERROR(msg) \ + if (FAILED(hr)) { \ + emit error(QCamera::CameraError, qt_error_string(hr)); \ + RETURN_VOID_IF_FAILED(msg); \ + } + +class CriticalSectionLocker +{ +public: + CriticalSectionLocker(CRITICAL_SECTION *section) + : m_section(section) + { + EnterCriticalSection(m_section); + } + ~CriticalSectionLocker() + { + LeaveCriticalSection(m_section); + } +private: + CRITICAL_SECTION *m_section; +}; + +class MediaStream : public RuntimeClass, IMFStreamSink, IMFMediaEventGenerator, IMFMediaTypeHandler> +{ +public: + MediaStream(IMFMediaType *type, IMFMediaSink *mediaSink, QWinRTCameraVideoRendererControl *videoRenderer) + : m_type(type), m_sink(mediaSink), m_videoRenderer(videoRenderer) + { + Q_ASSERT(m_videoRenderer); + + InitializeCriticalSectionEx(&m_mutex, 0, 0); + + HRESULT hr; + hr = MFCreateEventQueue(&m_eventQueue); + Q_ASSERT_SUCCEEDED(hr); + hr = MFAllocateSerialWorkQueue(MFASYNC_CALLBACK_QUEUE_STANDARD, &m_workQueueId); + Q_ASSERT_SUCCEEDED(hr); + } + + ~MediaStream() + { + CriticalSectionLocker locker(&m_mutex); + m_eventQueue->Shutdown(); + DeleteCriticalSection(&m_mutex); + } + + HRESULT RequestSample() + { + if (m_pendingSamples.load() < 3) { + m_pendingSamples.ref(); + return QueueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, Q_NULLPTR); + } + return S_OK; + } + + HRESULT __stdcall GetEvent(DWORD flags, IMFMediaEvent **event) Q_DECL_OVERRIDE + { + EnterCriticalSection(&m_mutex); + // Create an extra reference to avoid deadlock + ComPtr eventQueue = m_eventQueue; + LeaveCriticalSection(&m_mutex); + + return eventQueue->GetEvent(flags, event); + } + + HRESULT __stdcall BeginGetEvent(IMFAsyncCallback *callback, IUnknown *state) Q_DECL_OVERRIDE + { + CriticalSectionLocker locker(&m_mutex); + HRESULT hr = m_eventQueue->BeginGetEvent(callback, state); + return hr; + } + + HRESULT __stdcall EndGetEvent(IMFAsyncResult *result, IMFMediaEvent **event) Q_DECL_OVERRIDE + { + CriticalSectionLocker locker(&m_mutex); + return m_eventQueue->EndGetEvent(result, event); + } + + HRESULT __stdcall QueueEvent(MediaEventType eventType, const GUID &extendedType, HRESULT status, const PROPVARIANT *value) Q_DECL_OVERRIDE + { + CriticalSectionLocker locker(&m_mutex); + return m_eventQueue->QueueEventParamVar(eventType, extendedType, status, value); + } + + HRESULT __stdcall GetMediaSink(IMFMediaSink **mediaSink) Q_DECL_OVERRIDE + { + *mediaSink = m_sink; + return S_OK; + } + + HRESULT __stdcall GetIdentifier(DWORD *identifier) Q_DECL_OVERRIDE + { + *identifier = 0; + return S_OK; + } + + HRESULT __stdcall GetMediaTypeHandler(IMFMediaTypeHandler **handler) Q_DECL_OVERRIDE + { + return QueryInterface(IID_PPV_ARGS(handler)); + } + + HRESULT __stdcall ProcessSample(IMFSample *sample) Q_DECL_OVERRIDE + { + ComPtr buffer; + HRESULT hr = sample->GetBufferByIndex(0, &buffer); + RETURN_HR_IF_FAILED("Failed to get buffer from camera sample"); + ComPtr buffer2d; + hr = buffer.As(&buffer2d); + RETURN_HR_IF_FAILED("Failed to cast camera sample buffer to 2D buffer"); + + m_pendingSamples.deref(); + m_videoRenderer->queueBuffer(buffer2d.Get()); + + return hr; + } + + HRESULT __stdcall PlaceMarker(MFSTREAMSINK_MARKER_TYPE type, const PROPVARIANT *value, const PROPVARIANT *context) Q_DECL_OVERRIDE + { + Q_UNUSED(type); + Q_UNUSED(value); + QueueEvent(MEStreamSinkMarker, GUID_NULL, S_OK, context); + return S_OK; + } + + HRESULT __stdcall Flush() Q_DECL_OVERRIDE + { + m_videoRenderer->discardBuffers(); + m_pendingSamples.store(0); + return S_OK; + } + + HRESULT __stdcall IsMediaTypeSupported(IMFMediaType *type, IMFMediaType **) Q_DECL_OVERRIDE + { + HRESULT hr; + GUID majorType; + hr = type->GetMajorType(&majorType); + Q_ASSERT_SUCCEEDED(hr); + if (!IsEqualGUID(majorType, MFMediaType_Video)) + return MF_E_INVALIDMEDIATYPE; + return S_OK; + } + + HRESULT __stdcall GetMediaTypeCount(DWORD *typeCount) Q_DECL_OVERRIDE + { + *typeCount = 1; + return S_OK; + } + + HRESULT __stdcall GetMediaTypeByIndex(DWORD index, IMFMediaType **type) Q_DECL_OVERRIDE + { + if (index == 0) + return m_type.CopyTo(type); + return E_BOUNDS; + } + + HRESULT __stdcall SetCurrentMediaType(IMFMediaType *type) Q_DECL_OVERRIDE + { + if (FAILED(IsMediaTypeSupported(type, Q_NULLPTR))) + return MF_E_INVALIDREQUEST; + + m_type = type; + return S_OK; + } + + HRESULT __stdcall GetCurrentMediaType(IMFMediaType **type) Q_DECL_OVERRIDE + { + return m_type.CopyTo(type); + } + + HRESULT __stdcall GetMajorType(GUID *majorType) Q_DECL_OVERRIDE + { + return m_type->GetMajorType(majorType); + } + +private: + CRITICAL_SECTION m_mutex; + ComPtr m_type; + IMFMediaSink *m_sink; + ComPtr m_eventQueue; + DWORD m_workQueueId; + + QWinRTCameraVideoRendererControl *m_videoRenderer; + QAtomicInt m_pendingSamples; +}; + +class MediaSink : public RuntimeClass, IMediaExtension, IMFMediaSink, IMFClockStateSink> +{ +public: + MediaSink(IMediaEncodingProfile *encodingProfile, QWinRTCameraVideoRendererControl *videoRenderer) + : m_videoRenderer(videoRenderer) + { + HRESULT hr; + ComPtr videoProperties; + hr = encodingProfile->get_Video(&videoProperties); + RETURN_VOID_IF_FAILED("Failed to get video properties"); + ComPtr videoType; + hr = MFCreateMediaTypeFromProperties(videoProperties.Get(), &videoType); + RETURN_VOID_IF_FAILED("Failed to create video type"); + m_stream = Make(videoType.Get(), this, videoRenderer); + } + + ~MediaSink() + { + } + + HRESULT RequestSample() + { + return m_stream->RequestSample(); + } + + HRESULT __stdcall SetProperties(Collections::IPropertySet *configuration) Q_DECL_OVERRIDE + { + Q_UNUSED(configuration); + return E_NOTIMPL; + } + + HRESULT __stdcall GetCharacteristics(DWORD *characteristics) Q_DECL_OVERRIDE + { + *characteristics = MEDIASINK_FIXED_STREAMS | MEDIASINK_RATELESS; + return S_OK; + } + + HRESULT __stdcall AddStreamSink(DWORD streamSinkIdentifier, IMFMediaType *mediaType, IMFStreamSink **streamSink) Q_DECL_OVERRIDE + { + Q_UNUSED(streamSinkIdentifier); + Q_UNUSED(mediaType); + Q_UNUSED(streamSink); + return E_NOTIMPL; + } + + HRESULT __stdcall RemoveStreamSink(DWORD streamSinkIdentifier) Q_DECL_OVERRIDE + { + Q_UNUSED(streamSinkIdentifier); + return E_NOTIMPL; + } + + HRESULT __stdcall GetStreamSinkCount(DWORD *streamSinkCount) Q_DECL_OVERRIDE + { + *streamSinkCount = 1; + return S_OK; + } + + HRESULT __stdcall GetStreamSinkByIndex(DWORD index, IMFStreamSink **streamSink) Q_DECL_OVERRIDE + { + if (index == 0) + return m_stream.CopyTo(streamSink); + return MF_E_INVALIDINDEX; + } + + HRESULT __stdcall GetStreamSinkById(DWORD streamSinkIdentifier, IMFStreamSink **streamSink) Q_DECL_OVERRIDE + { + // ID and index are always 0 + HRESULT hr = GetStreamSinkByIndex(streamSinkIdentifier, streamSink); + return hr == MF_E_INVALIDINDEX ? MF_E_INVALIDSTREAMNUMBER : hr; + } + + HRESULT __stdcall SetPresentationClock(IMFPresentationClock *presentationClock) Q_DECL_OVERRIDE + { + HRESULT hr = S_OK; + m_presentationClock = presentationClock; + if (m_presentationClock) + hr = m_presentationClock->AddClockStateSink(this); + return hr; + } + + HRESULT __stdcall GetPresentationClock(IMFPresentationClock **presentationClock) Q_DECL_OVERRIDE + { + return m_presentationClock.CopyTo(presentationClock); + } + + HRESULT __stdcall Shutdown() Q_DECL_OVERRIDE + { + m_stream->Flush(); + m_videoRenderer->setActive(false); + return m_presentationClock->Stop(); + } + + HRESULT __stdcall OnClockStart(MFTIME systemTime, LONGLONG clockStartOffset) Q_DECL_OVERRIDE + { + Q_UNUSED(systemTime); + Q_UNUSED(clockStartOffset); + + m_videoRenderer->setActive(true); + + return S_OK; + } + + HRESULT __stdcall OnClockStop(MFTIME systemTime) Q_DECL_OVERRIDE + { + Q_UNUSED(systemTime); + + m_videoRenderer->setActive(false); + + return m_stream->QueueEvent(MEStreamSinkStopped, GUID_NULL, S_OK, Q_NULLPTR); + } + + HRESULT __stdcall OnClockPause(MFTIME systemTime) Q_DECL_OVERRIDE + { + Q_UNUSED(systemTime); + + m_videoRenderer->setActive(false); + + return m_stream->QueueEvent(MEStreamSinkPaused, GUID_NULL, S_OK, Q_NULLPTR); + } + + HRESULT __stdcall OnClockRestart(MFTIME systemTime) Q_DECL_OVERRIDE + { + Q_UNUSED(systemTime); + + m_videoRenderer->setActive(true); + + return m_stream->QueueEvent(MEStreamSinkStarted, GUID_NULL, S_OK, Q_NULLPTR); + } + + HRESULT __stdcall OnClockSetRate(MFTIME systemTime, float rate) Q_DECL_OVERRIDE + { + Q_UNUSED(systemTime); + Q_UNUSED(rate); + return E_NOTIMPL; + } + +private: + ComPtr m_stream; + ComPtr m_presentationClock; + + QWinRTCameraVideoRendererControl *m_videoRenderer; +}; + +class QWinRTCameraControlPrivate +{ +public: + QCamera::State state; + QCamera::Status status; + QCamera::CaptureModes captureMode; + + ComPtr capture; + ComPtr capturePreview; + EventRegistrationToken captureFailedCookie; + EventRegistrationToken recordLimitationCookie; + + ComPtr encodingProfileFactory; + + ComPtr encodingProfile; + ComPtr mediaSink; + + QSize size; + QPointer videoRenderer; + QPointer videoDeviceSelector; + QPointer imageCaptureControl; +}; + +QWinRTCameraControl::QWinRTCameraControl(QObject *parent) + : QCameraControl(parent), d_ptr(new QWinRTCameraControlPrivate) +{ + Q_D(QWinRTCameraControl); + + d->state = QCamera::UnloadedState; + d->status = QCamera::UnloadedStatus; + d->captureMode = QCamera::CaptureStillImage; + d->captureFailedCookie.value = 0; + d->recordLimitationCookie.value = 0; + d->videoRenderer = new QWinRTCameraVideoRendererControl(d->size, this); + connect(d->videoRenderer, &QWinRTCameraVideoRendererControl::bufferRequested, + this, &QWinRTCameraControl::onBufferRequested); + d->videoDeviceSelector = new QWinRTVideoDeviceSelectorControl(this); + d->imageCaptureControl = new QWinRTCameraImageCaptureControl(this); +} + +QWinRTCameraControl::~QWinRTCameraControl() +{ + setState(QCamera::UnloadedState); +} + +QCamera::State QWinRTCameraControl::state() const +{ + Q_D(const QWinRTCameraControl); + return d->state; +} + +void QWinRTCameraControl::setState(QCamera::State state) +{ + Q_D(QWinRTCameraControl); + + if (d->state == state) + return; + + HRESULT hr; + switch (state) { + case QCamera::ActiveState: { + // Capture has not been created or initialized + if (d->state == QCamera::UnloadedState) { + hr = initialize(); + RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture"); + } + Q_ASSERT(d->state == QCamera::LoadedState); + + d->mediaSink = Make(d->encodingProfile.Get(), d->videoRenderer); + ComPtr op; + hr = d->capturePreview->StartPreviewToCustomSinkAsync(d->encodingProfile.Get(), d->mediaSink.Get(), &op); + RETURN_VOID_AND_EMIT_ERROR("Failed to initiate capture"); + if (d->status != QCamera::StartingStatus) { + d->status = QCamera::StartingStatus; + emit statusChanged(d->status); + } + + hr = QWinRTFunctions::await(op); + if (FAILED(hr)) { + emit error(QCamera::CameraError, qt_error_string(hr)); + setState(QCamera::UnloadedState); // Unload everything, as initialize() will need be called again + return; + } + + d->state = QCamera::ActiveState; + emit stateChanged(d->state); + d->status = QCamera::ActiveStatus; + emit statusChanged(d->status); + break; + } + case QCamera::LoadedState: { + // If moving from unloaded, initialize the camera + if (d->state == QCamera::UnloadedState) { + hr = initialize(); + RETURN_VOID_AND_EMIT_ERROR("Failed to initialize media capture"); + } + // fall through + } + case QCamera::UnloadedState: { + // Stop the camera if it is running (transition to LoadedState) + if (d->status == QCamera::ActiveStatus) { + ComPtr op; + hr = d->capturePreview->StopPreviewAsync(&op); + RETURN_VOID_AND_EMIT_ERROR("Failed to stop camera preview"); + if (d->status != QCamera::StoppingStatus) { + d->status = QCamera::StoppingStatus; + emit statusChanged(d->status); + } + Q_ASSERT_SUCCEEDED(hr); + hr = QWinRTFunctions::await(op); // Synchronize unloading + if (FAILED(hr)) + emit error(QCamera::InvalidRequestError, qt_error_string(hr)); + + d->mediaSink->Shutdown(); + d->mediaSink.Reset(); + + d->state = QCamera::LoadedState; + emit stateChanged(d->state); + + d->status = QCamera::LoadedStatus; + emit statusChanged(d->status); + } + // Completely unload if needed + if (state == QCamera::UnloadedState) { + if (!d->capture) // Already unloaded + break; + + if (d->status != QCamera::UnloadingStatus) { + d->status = QCamera::UnloadingStatus; + emit statusChanged(d->status); + } + + if (d->capture && d->captureFailedCookie.value) { + hr = d->capture->remove_Failed(d->captureFailedCookie); + Q_ASSERT_SUCCEEDED(hr); + d->captureFailedCookie.value = 0; + } + if (d->capture && d->recordLimitationCookie.value) { + d->capture->remove_RecordLimitationExceeded(d->recordLimitationCookie); + Q_ASSERT_SUCCEEDED(hr); + d->recordLimitationCookie.value = 0; + } + ComPtr capture; + hr = d->capture.As(&capture); + Q_ASSERT_SUCCEEDED(hr); + hr = capture->Close(); + RETURN_VOID_AND_EMIT_ERROR("Failed to close the capture manger"); + d->capture.Reset(); + if (d->state != QCamera::UnloadedState) { + d->state = QCamera::UnloadedState; + emit stateChanged(d->state); + } + if (d->status != QCamera::UnloadedStatus) { + d->status = QCamera::UnloadedStatus; + emit statusChanged(d->status); + } + } + break; + } + default: + break; + } +} + +QCamera::Status QWinRTCameraControl::status() const +{ + Q_D(const QWinRTCameraControl); + return d->status; +} + +QCamera::CaptureModes QWinRTCameraControl::captureMode() const +{ + Q_D(const QWinRTCameraControl); + return d->captureMode; +} + +void QWinRTCameraControl::setCaptureMode(QCamera::CaptureModes mode) +{ + Q_D(QWinRTCameraControl); + + if (d->captureMode == mode) + return; + + if (!isCaptureModeSupported(mode)) { + qWarning("Unsupported capture mode: %d", mode); + return; + } + + d->captureMode = mode; + emit captureModeChanged(d->captureMode); +} + +bool QWinRTCameraControl::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + return mode >= QCamera::CaptureViewfinder && mode <= QCamera::CaptureStillImage; +} + +bool QWinRTCameraControl::canChangeProperty(QCameraControl::PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(changeType); + + return status == QCamera::UnloadedStatus; // For now, assume shutdown is required for all property changes +} + +QVideoRendererControl *QWinRTCameraControl::videoRenderer() const +{ + Q_D(const QWinRTCameraControl); + return d->videoRenderer; +} + +QVideoDeviceSelectorControl *QWinRTCameraControl::videoDeviceSelector() const +{ + Q_D(const QWinRTCameraControl); + return d->videoDeviceSelector; +} + +QCameraImageCaptureControl *QWinRTCameraControl::imageCaptureControl() const +{ + Q_D(const QWinRTCameraControl); + return d->imageCaptureControl; +} + +IMediaCapture *QWinRTCameraControl::handle() const +{ + Q_D(const QWinRTCameraControl); + return d->capture.Get(); +} + +QSize QWinRTCameraControl::imageSize() const +{ + Q_D(const QWinRTCameraControl); + return d->size; +} + +void QWinRTCameraControl::onBufferRequested() +{ + Q_D(QWinRTCameraControl); + + if (d->mediaSink) + d->mediaSink->RequestSample(); +} + +HRESULT QWinRTCameraControl::initialize() +{ + Q_D(QWinRTCameraControl); + + if (d->status != QCamera::LoadingStatus) { + d->status = QCamera::LoadingStatus; + emit statusChanged(d->status); + } + + HRESULT hr; + ComPtr capture; + hr = RoActivateInstance(Wrappers::HString::MakeReference(RuntimeClass_Windows_Media_Capture_MediaCapture).Get(), + &capture); + Q_ASSERT_SUCCEEDED(hr); + hr = capture.As(&d->capture); + Q_ASSERT_SUCCEEDED(hr); + hr = d->capture.As(&d->capturePreview); + Q_ASSERT_SUCCEEDED(hr); + hr = d->capture->add_Failed(Callback(this, &QWinRTCameraControl::onCaptureFailed).Get(), + &d->captureFailedCookie); + Q_ASSERT_SUCCEEDED(hr); + hr = d->capture->add_RecordLimitationExceeded(Callback(this, &QWinRTCameraControl::onRecordLimitationExceeded).Get(), + &d->recordLimitationCookie); + Q_ASSERT_SUCCEEDED(hr); + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_MediaEncodingProfile).Get(), + IID_PPV_ARGS(&d->encodingProfileFactory)); + Q_ASSERT_SUCCEEDED(hr); + + int deviceIndex = d->videoDeviceSelector->selectedDevice(); + if (deviceIndex < 0) + deviceIndex = d->videoDeviceSelector->defaultDevice(); + + const QString deviceName = d->videoDeviceSelector->deviceName(deviceIndex); + if (deviceName.isEmpty()) { + qWarning("No video device available or selected."); + return E_FAIL; + } + + ComPtr settings; + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_Capture_MediaCaptureInitializationSettings).Get(), + &settings); + Q_ASSERT_SUCCEEDED(hr); + HStringReference deviceId(reinterpret_cast(deviceName.utf16()), deviceName.length()); + hr = settings->put_VideoDeviceId(deviceId.Get()); + Q_ASSERT_SUCCEEDED(hr); + + hr = settings->put_StreamingCaptureMode(StreamingCaptureMode_Video); + Q_ASSERT_SUCCEEDED(hr); + + hr = settings->put_PhotoCaptureSource(PhotoCaptureSource_Auto); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr op; + hr = d->capture->InitializeWithSettingsAsync(settings.Get(), &op); + RETURN_HR_IF_FAILED("Failed to begin initialization of media capture manager"); + hr = QWinRTFunctions::await(op, QWinRTFunctions::ProcessThreadEvents); + if (hr == E_ACCESSDENIED) { + qWarning("Access denied when initializing the media capture manager. " + "Check your manifest settings for microphone and webcam access."); + } + RETURN_HR_IF_FAILED("Failed to initialize media capture manager"); + + ComPtr videoDeviceController; + hr = d->capture->get_VideoDeviceController(&videoDeviceController); + Q_ASSERT_SUCCEEDED(hr); + ComPtr deviceController; + hr = videoDeviceController.As(&deviceController); + Q_ASSERT_SUCCEEDED(hr); + ComPtr> encodingPropertiesList; + hr = deviceController->GetAvailableMediaStreamProperties(MediaStreamType_Photo, &encodingPropertiesList); + Q_ASSERT_SUCCEEDED(hr); + + d->size = QSize(); + ComPtr videoEncodingProperties; + quint32 encodingPropertiesListSize; + hr = encodingPropertiesList->get_Size(&encodingPropertiesListSize); + Q_ASSERT_SUCCEEDED(hr); + for (quint32 i = 0; i < encodingPropertiesListSize; ++i) { + ComPtr properties; + hr = encodingPropertiesList->GetAt(i, &properties); + Q_ASSERT_SUCCEEDED(hr); + ComPtr videoProperties; + hr = properties.As(&videoEncodingProperties); + Q_ASSERT_SUCCEEDED(hr); + UINT32 width, height; + hr = videoEncodingProperties->get_Width(&width); + Q_ASSERT_SUCCEEDED(hr); + hr = videoEncodingProperties->get_Height(&height); + Q_ASSERT_SUCCEEDED(hr); + // Choose the highest-quality format + if (int(width * height) > d->size.width() * d->size.height()) { + d->size = QSize(width, height); + videoEncodingProperties = videoProperties; + } + } + + if (!videoEncodingProperties || d->size.isEmpty()) { + hr = MF_E_INVALID_FORMAT; + RETURN_HR_IF_FAILED("Failed to find a suitable video format"); + } + + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_MediaEncodingProfile).Get(), + &d->encodingProfile); + Q_ASSERT_SUCCEEDED(hr); + hr = d->encodingProfile->put_Video(videoEncodingProperties.Get()); + Q_ASSERT_SUCCEEDED(hr); + if (d->videoRenderer) + d->videoRenderer->setSize(d->size); + + if (SUCCEEDED(hr) && d->state != QCamera::LoadedState) { + d->state = QCamera::LoadedState; + emit stateChanged(d->state); + } + if (SUCCEEDED(hr) && d->status != QCamera::LoadedStatus) { + d->status = QCamera::LoadedStatus; + emit statusChanged(d->status); + } + return hr; +} + +HRESULT QWinRTCameraControl::onCaptureFailed(IMediaCapture *, IMediaCaptureFailedEventArgs *args) +{ + HRESULT hr; + UINT32 code; + hr = args->get_Code(&code); + RETURN_HR_IF_FAILED("Failed to get error code"); + HString message; + args->get_Message(message.GetAddressOf()); + RETURN_HR_IF_FAILED("Failed to get error message"); + quint32 messageLength; + const wchar_t *messageBuffer = message.GetRawBuffer(&messageLength); + emit error(QCamera::CameraError, QString::fromWCharArray(messageBuffer, messageLength)); + setState(QCamera::LoadedState); + return S_OK; +} + +HRESULT QWinRTCameraControl::onRecordLimitationExceeded(IMediaCapture *) +{ + emit error(QCamera::CameraError, QStringLiteral("Recording limit exceeded.")); + setState(QCamera::LoadedState); + return S_OK; +} diff --git a/src/plugins/winrt/qwinrtcameracontrol.h b/src/plugins/winrt/qwinrtcameracontrol.h new file mode 100644 index 00000000..e75f7e47 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameracontrol.h @@ -0,0 +1,111 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTCAMERACONTROL_H +#define QWINRTCAMERACONTROL_H + +#include +#include + +namespace ABI { + namespace Windows { + namespace Media { + namespace Capture { + struct IMediaCapture; + struct IMediaCaptureFailedEventArgs; + } + } + namespace Foundation { + struct IAsyncAction; + enum class AsyncStatus; + } + } +} + +QT_BEGIN_NAMESPACE + +class QVideoRendererControl; +class QVideoDeviceSelectorControl; +class QCameraImageCaptureControl; + +class QWinRTCameraControlPrivate; +class QWinRTCameraControl : public QCameraControl +{ + Q_OBJECT +public: + explicit QWinRTCameraControl(QObject *parent = 0); + ~QWinRTCameraControl(); + + QCamera::State state() const Q_DECL_OVERRIDE; + void setState(QCamera::State state) Q_DECL_OVERRIDE; + + QCamera::Status status() const Q_DECL_OVERRIDE; + + QCamera::CaptureModes captureMode() const Q_DECL_OVERRIDE; + void setCaptureMode(QCamera::CaptureModes mode) Q_DECL_OVERRIDE; + bool isCaptureModeSupported(QCamera::CaptureModes mode) const Q_DECL_OVERRIDE; + + bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const Q_DECL_OVERRIDE; + + QVideoRendererControl *videoRenderer() const; + QVideoDeviceSelectorControl *videoDeviceSelector() const; + QCameraImageCaptureControl *imageCaptureControl() const; + + ABI::Windows::Media::Capture::IMediaCapture *handle() const; + QSize imageSize() const; + +private slots: + void onBufferRequested(); + +private: + HRESULT enumerateDevices(); + HRESULT initialize(); + HRESULT onCaptureFailed(ABI::Windows::Media::Capture::IMediaCapture *, + ABI::Windows::Media::Capture::IMediaCaptureFailedEventArgs *); + HRESULT onRecordLimitationExceeded(ABI::Windows::Media::Capture::IMediaCapture *); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTCameraControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTCAMERACONTROL_H diff --git a/src/plugins/winrt/qwinrtcameraimagecapturecontrol.cpp b/src/plugins/winrt/qwinrtcameraimagecapturecontrol.cpp new file mode 100644 index 00000000..f5151c61 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameraimagecapturecontrol.cpp @@ -0,0 +1,280 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtcameraimagecapturecontrol.h" +#include "qwinrtcameracontrol.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Media::Capture; +using namespace ABI::Windows::Media::Devices; +using namespace ABI::Windows::Media::MediaProperties; +using namespace ABI::Windows::Storage::Streams; +using namespace ABI::Windows::Graphics::Imaging; + +QT_USE_NAMESPACE + +#define wchar(str) reinterpret_cast(str.utf16()) + +struct QWinRTCameraImageCaptureControlGlobal +{ + QWinRTCameraImageCaptureControlGlobal() + { + HRESULT hr; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Media_MediaProperties_ImageEncodingProperties).Get(), + &encodingPropertiesFactory); + Q_ASSERT_SUCCEEDED(hr); + + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_Buffer).Get(), + &bufferFactory); + Q_ASSERT_SUCCEEDED(hr); + + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_DataReader).Get(), + &dataReaderFactory); + } + + ComPtr encodingPropertiesFactory; + ComPtr bufferFactory; + ComPtr dataReaderFactory; +}; +Q_GLOBAL_STATIC(QWinRTCameraImageCaptureControlGlobal, g) + +struct CaptureRequest +{ + quint16 id; + QString fileName; + ComPtr imageFormat; + ComPtr stream; + ComPtr op; +}; + +class QWinRTCameraImageCaptureControlPrivate +{ +public: + QPointer cameraControl; + QHash requests; + quint16 currentCaptureId; + QMediaStorageLocation location; + + void onCameraStateChanged() + { + + } +}; + +QWinRTCameraImageCaptureControl::QWinRTCameraImageCaptureControl(QWinRTCameraControl *parent) + : QCameraImageCaptureControl(parent), d_ptr(new QWinRTCameraImageCaptureControlPrivate) +{ + Q_D(QWinRTCameraImageCaptureControl); + + d->cameraControl = parent; + connect(d->cameraControl, &QCameraControl::stateChanged, + this, &QWinRTCameraImageCaptureControl::readyForCaptureChanged); + d->currentCaptureId = 0; +} + +bool QWinRTCameraImageCaptureControl::isReadyForCapture() const +{ + Q_D(const QWinRTCameraImageCaptureControl); + return d->cameraControl->state() == QCamera::ActiveState; +} + +QCameraImageCapture::DriveMode QWinRTCameraImageCaptureControl::driveMode() const +{ + return QCameraImageCapture::SingleImageCapture; +} + +void QWinRTCameraImageCaptureControl::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + Q_UNUSED(mode); +} + +int QWinRTCameraImageCaptureControl::capture(const QString &fileName) +{ + Q_D(QWinRTCameraImageCaptureControl); + + ++d->currentCaptureId; + IMediaCapture *capture = d->cameraControl->handle(); + if (!capture) { + emit error(d->currentCaptureId, QCameraImageCapture::NotReadyError, tr("Camera not ready")); + return -1; + } + + CaptureRequest request = { + d->currentCaptureId, + d->location.generateFileName(fileName, QMediaStorageLocation::Pictures, QStringLiteral("IMG_"), + fileName.isEmpty() ? QStringLiteral("jpg") : QFileInfo(fileName).suffix()) + }; + + HRESULT hr; + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Storage_Streams_InMemoryRandomAccessStream).Get(), + &request.stream); + Q_ASSERT_SUCCEEDED(hr); + + hr = g->encodingPropertiesFactory->CreateBmp(&request.imageFormat); + Q_ASSERT_SUCCEEDED(hr); + + const QSize imageSize = d->cameraControl->imageSize(); + hr = request.imageFormat->put_Width(imageSize.width()); + Q_ASSERT_SUCCEEDED(hr); + hr = request.imageFormat->put_Height(imageSize.height()); + Q_ASSERT_SUCCEEDED(hr); + + hr = capture->CapturePhotoToStreamAsync(request.imageFormat.Get(), request.stream.Get(), &request.op); + Q_ASSERT_SUCCEEDED(hr); + d->requests.insert(request.op.Get(), request); + + hr = request.op->put_Completed(Callback( + this, &QWinRTCameraImageCaptureControl::onCaptureCompleted).Get()); + Q_ASSERT_SUCCEEDED(hr); + + return request.id; +} + +void QWinRTCameraImageCaptureControl::cancelCapture() +{ + Q_D(QWinRTCameraImageCaptureControl); + + QHash::iterator it = d->requests.begin(); + while (it != d->requests.end()) { + ComPtr info; + it->op.As(&info); + info->Cancel(); + it = d->requests.erase(it); + } +} + +HRESULT QWinRTCameraImageCaptureControl::onCaptureCompleted(IAsyncAction *asyncInfo, AsyncStatus status) +{ + Q_D(QWinRTCameraImageCaptureControl); + + if (status == Canceled || !d->requests.contains(asyncInfo)) + return S_OK; + + CaptureRequest request = d->requests.take(asyncInfo); + + HRESULT hr; + if (status == Error) { + hr = asyncInfo->GetResults(); + emit error(request.id, QCameraImageCapture::ResourceError, qt_error_string(hr)); + return S_OK; + } + + quint64 dataLength; + hr = request.stream->get_Size(&dataLength); + Q_ASSERT_SUCCEEDED(hr); + if (dataLength == 0 || dataLength > INT_MAX) { + emit error(request.id, QCameraImageCapture::FormatError, tr("Invalid photo data length.")); + return S_OK; + } + + ComPtr bitmapFactory; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Graphics_Imaging_BitmapDecoder).Get(), + &bitmapFactory); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr> op; + hr = bitmapFactory->CreateAsync(request.stream.Get(), &op); + Q_ASSERT_SUCCEEDED(hr); + ComPtr decoder; + hr = QWinRTFunctions::await(op, decoder.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr> op2; + hr = decoder->GetFrameAsync(0, &op2); + Q_ASSERT_SUCCEEDED(hr); + ComPtr frame; + hr = QWinRTFunctions::await(op2, frame.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr transform; + hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Graphics_Imaging_BitmapTransform).Get(), + &transform); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr> op3; + hr = frame->GetPixelDataTransformedAsync(BitmapPixelFormat_Rgba8, BitmapAlphaMode_Straight, + transform.Get(), ExifOrientationMode_IgnoreExifOrientation, + ColorManagementMode_DoNotColorManage, &op3); + Q_ASSERT_SUCCEEDED(hr); + ComPtr pixelDataProvider; + hr = QWinRTFunctions::await(op3, pixelDataProvider.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + UINT32 pixelDataSize; + BYTE *pixelData; + hr = pixelDataProvider->DetachPixelData(&pixelDataSize, &pixelData); + + UINT32 pixelHeight; + hr = frame->get_PixelHeight(&pixelHeight); + Q_ASSERT_SUCCEEDED(hr); + UINT32 pixelWidth; + hr = frame->get_PixelWidth(&pixelWidth); + Q_ASSERT_SUCCEEDED(hr); + const QImage image(pixelData, pixelWidth, pixelHeight, QImage::Format_RGBA8888, + reinterpret_cast(&CoTaskMemFree), pixelData); + emit imageCaptured(request.id, image); + if (image.save(request.fileName)) + emit imageSaved(request.id, request.fileName); + else + emit error(request.id, QCameraImageCapture::ResourceError, tr("Image saving failed")); + + return S_OK; +} diff --git a/src/plugins/winrt/qwinrtcameraimagecapturecontrol.h b/src/plugins/winrt/qwinrtcameraimagecapturecontrol.h new file mode 100644 index 00000000..5150e4d3 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameraimagecapturecontrol.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTCAMERAIMAGECAPTURECONTROL_H +#define QWINRTCAMERAIMAGECAPTURECONTROL_H + +#include +#include + +namespace ABI { + namespace Windows { + namespace Foundation { + struct IAsyncAction; + enum class AsyncStatus; + } + } +} + +QT_BEGIN_NAMESPACE + +class QWinRTCameraControl; + +class QWinRTCameraImageCaptureControlPrivate; +class QWinRTCameraImageCaptureControl : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + explicit QWinRTCameraImageCaptureControl(QWinRTCameraControl *parent); + + bool isReadyForCapture() const Q_DECL_OVERRIDE; + + QCameraImageCapture::DriveMode driveMode() const Q_DECL_OVERRIDE; + void setDriveMode(QCameraImageCapture::DriveMode mode) Q_DECL_OVERRIDE; + + int capture(const QString &fileName) Q_DECL_OVERRIDE; + void cancelCapture() Q_DECL_OVERRIDE; + +private: + HRESULT onCaptureCompleted(ABI::Windows::Foundation::IAsyncAction *, + ABI::Windows::Foundation::AsyncStatus); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTCameraImageCaptureControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTCAMERAIMAGECAPTURECONTROL_H diff --git a/src/plugins/winrt/qwinrtcamerainfocontrol.cpp b/src/plugins/winrt/qwinrtcamerainfocontrol.cpp new file mode 100644 index 00000000..c16b83be --- /dev/null +++ b/src/plugins/winrt/qwinrtcamerainfocontrol.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtcamerainfocontrol.h" +#include "qwinrtvideodeviceselectorcontrol.h" + +QT_USE_NAMESPACE + +QWinRTCameraInfoControl::QWinRTCameraInfoControl(QObject *parent) + : QCameraInfoControl(parent) +{ +} + +QCamera::Position QWinRTCameraInfoControl::cameraPosition(const QString &deviceName) const +{ + return QWinRTVideoDeviceSelectorControl::cameraPosition(deviceName); +} + +int QWinRTCameraInfoControl::cameraOrientation(const QString &deviceName) const +{ + return QWinRTVideoDeviceSelectorControl::cameraOrientation(deviceName); +} diff --git a/src/plugins/winrt/qwinrtcamerainfocontrol.h b/src/plugins/winrt/qwinrtcamerainfocontrol.h new file mode 100644 index 00000000..bf430f03 --- /dev/null +++ b/src/plugins/winrt/qwinrtcamerainfocontrol.h @@ -0,0 +1,61 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTCAMERAINFOCONTROL_H +#define QWINRTCAMERAINFOCONTROL_H + +#include + +QT_BEGIN_NAMESPACE + +class QWinRTCameraInfoControl : public QCameraInfoControl +{ + Q_OBJECT +public: + explicit QWinRTCameraInfoControl(QObject *parent = 0); + + QCamera::Position cameraPosition(const QString &deviceName) const Q_DECL_OVERRIDE; + int cameraOrientation(const QString &deviceName) const Q_DECL_OVERRIDE; +}; + +QT_END_NAMESPACE + +#endif // QWINRTCAMERAINFOCONTROL_H diff --git a/src/plugins/winrt/qwinrtcameraservice.cpp b/src/plugins/winrt/qwinrtcameraservice.cpp new file mode 100644 index 00000000..239a1e88 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameraservice.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtcameraservice.h" +#include "qwinrtcameracontrol.h" +#include "qwinrtcamerainfocontrol.h" + +#include +#include +#include +#include +#include +#include + +QT_USE_NAMESPACE + +class QWinRTCameraServicePrivate +{ +public: + QPointer cameraControl; + QPointer cameraInfoControl; +}; + +QWinRTCameraService::QWinRTCameraService(QObject *parent) + : QMediaService(parent), d_ptr(new QWinRTCameraServicePrivate) +{ +} + +QMediaControl *QWinRTCameraService::requestControl(const char *name) +{ + Q_D(QWinRTCameraService); + + if (qstrcmp(name, QCameraControl_iid) == 0) { + if (!d->cameraControl) + d->cameraControl = new QWinRTCameraControl(this); + return d->cameraControl; + } + + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + if (d->cameraControl) + return d->cameraControl->videoRenderer(); + } + + if (qstrcmp(name, QVideoDeviceSelectorControl_iid) == 0) { + if (d->cameraControl) + return d->cameraControl->videoDeviceSelector(); + } + + if (qstrcmp(name, QCameraInfoControl_iid) == 0) { + if (!d->cameraInfoControl) + d->cameraInfoControl = new QWinRTCameraInfoControl(this); + return d->cameraInfoControl; + } + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) { + if (d->cameraControl) + return d->cameraControl->imageCaptureControl(); + } + + return Q_NULLPTR; +} + +void QWinRTCameraService::releaseControl(QMediaControl *control) +{ + Q_UNUSED(control); +} diff --git a/src/plugins/winrt/qwinrtcameraservice.h b/src/plugins/winrt/qwinrtcameraservice.h new file mode 100644 index 00000000..19e93a81 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameraservice.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTCAMERASERVICE_H +#define QWINRTCAMERASERVICE_H + +#include + +QT_BEGIN_NAMESPACE + +class QWinRTCameraServicePrivate; +class QWinRTCameraService : public QMediaService +{ + Q_OBJECT +public: + explicit QWinRTCameraService(QObject *parent = 0); + + QMediaControl *requestControl(const char *name) Q_DECL_OVERRIDE; + void releaseControl(QMediaControl *control) Q_DECL_OVERRIDE; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTCameraService) +}; + +QT_END_NAMESPACE + +#endif // QWINRTCAMERASERVICE_H diff --git a/src/plugins/winrt/qwinrtcameravideorenderercontrol.cpp b/src/plugins/winrt/qwinrtcameravideorenderercontrol.cpp new file mode 100644 index 00000000..e7e75da2 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameravideorenderercontrol.cpp @@ -0,0 +1,204 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtcameravideorenderercontrol.h" + +#include +#include +#include + +#include +#include +#include +using namespace Microsoft::WRL; + +QT_USE_NAMESPACE + +class D3DVideoBlitter +{ +public: + D3DVideoBlitter(ID3D11Device *device, ID3D11Texture2D *target) + : m_d3dDevice(device), m_target(target) + { + HRESULT hr; + ComPtr targetResource; + hr = target->QueryInterface(IID_PPV_ARGS(&targetResource)); + Q_ASSERT_SUCCEEDED(hr); + HANDLE sharedHandle; + hr = targetResource->GetSharedHandle(&sharedHandle); + Q_ASSERT_SUCCEEDED(hr); + hr = m_d3dDevice->OpenSharedResource(sharedHandle, IID_PPV_ARGS(&m_targetTexture)); + Q_ASSERT_SUCCEEDED(hr); + hr = m_d3dDevice.As(&m_videoDevice); + Q_ASSERT_SUCCEEDED(hr); + } + + ID3D11Device *device() const + { + return m_d3dDevice.Get(); + } + + ID3D11Texture2D *target() const + { + return m_target; + } + + void blit(ID3D11Texture2D *texture) + { + HRESULT hr; + D3D11_TEXTURE2D_DESC desc; + texture->GetDesc(&desc); + if (!m_videoEnumerator) { + D3D11_VIDEO_PROCESSOR_CONTENT_DESC videoProcessorDesc = { + D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE, + { 0 }, desc.Width, desc.Height, + { 0 }, desc.Width, desc.Height, + D3D11_VIDEO_USAGE_PLAYBACK_NORMAL + }; + hr = m_videoDevice->CreateVideoProcessorEnumerator(&videoProcessorDesc, &m_videoEnumerator); + RETURN_VOID_IF_FAILED("Failed to create video enumerator"); + } + + if (!m_videoProcessor) { + hr = m_videoDevice->CreateVideoProcessor(m_videoEnumerator.Get(), 0, &m_videoProcessor); + RETURN_VOID_IF_FAILED("Failed to create video processor"); + } + + if (!m_outputView) { + D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC outputDesc = { D3D11_VPOV_DIMENSION_TEXTURE2D }; + hr = m_videoDevice->CreateVideoProcessorOutputView( + m_targetTexture.Get(), m_videoEnumerator.Get(), &outputDesc, &m_outputView); + RETURN_VOID_IF_FAILED("Failed to create video output view"); + } + + D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC inputViewDesc = { + 0, D3D11_VPIV_DIMENSION_TEXTURE2D, { 0, 0 } + }; + ComPtr inputView; + hr = m_videoDevice->CreateVideoProcessorInputView( + texture, m_videoEnumerator.Get(), &inputViewDesc, &inputView); + RETURN_VOID_IF_FAILED("Failed to create video input view"); + + ComPtr context; + ComPtr videoContext; + m_d3dDevice->GetImmediateContext(&context); + hr = context.As(&videoContext); + RETURN_VOID_IF_FAILED("Failed to get video context"); + + D3D11_VIDEO_PROCESSOR_STREAM stream = { TRUE }; + stream.pInputSurface = inputView.Get(); + hr = videoContext->VideoProcessorBlt( + m_videoProcessor.Get(), m_outputView.Get(), 0, 1, &stream); + RETURN_VOID_IF_FAILED("Failed to get blit video frame"); + } + +private: + ComPtr m_d3dDevice; + ComPtr m_targetTexture; + ID3D11Texture2D *m_target; + ComPtr m_videoDevice; + ComPtr m_videoEnumerator; + ComPtr m_videoProcessor; + ComPtr m_outputView; +}; + +class QWinRTCameraVideoRendererControlPrivate +{ +public: + QScopedPointer blitter; + QVector> buffers; +}; + +QWinRTCameraVideoRendererControl::QWinRTCameraVideoRendererControl(const QSize &size, QObject *parent) + : QWinRTAbstractVideoRendererControl(size, parent), d_ptr(new QWinRTCameraVideoRendererControlPrivate) +{ +} + +QWinRTCameraVideoRendererControl::~QWinRTCameraVideoRendererControl() +{ + shutdown(); +} + +bool QWinRTCameraVideoRendererControl::render(ID3D11Texture2D *target) +{ + Q_D(QWinRTCameraVideoRendererControl); + + if (d->buffers.isEmpty()) { + emit bufferRequested(); + return false; + } + + HRESULT hr; + ComPtr buffer = d->buffers.takeFirst(); + + ComPtr sourceTexture; + ComPtr dxgiBuffer; + hr = buffer.As(&dxgiBuffer); + Q_ASSERT_SUCCEEDED(hr); + hr = dxgiBuffer->GetResource(IID_PPV_ARGS(&sourceTexture)); + if (FAILED(hr)) { + qErrnoWarning(hr, "The video frame does not support texture output; aborting rendering."); + return false; + } + + ComPtr device; + sourceTexture->GetDevice(&device); + if (!d->blitter || d->blitter->device() != device.Get() || d->blitter->target() != target) + d->blitter.reset(new D3DVideoBlitter(device.Get(), target)); + + d->blitter->blit(sourceTexture.Get()); + + emit bufferRequested(); + return true; +} + +void QWinRTCameraVideoRendererControl::queueBuffer(IMF2DBuffer *buffer) +{ + Q_D(QWinRTCameraVideoRendererControl); + Q_ASSERT(buffer); + d->buffers.append(buffer); +} + +void QWinRTCameraVideoRendererControl::discardBuffers() +{ + Q_D(QWinRTCameraVideoRendererControl); + d->buffers.clear(); +} diff --git a/src/plugins/winrt/qwinrtcameravideorenderercontrol.h b/src/plugins/winrt/qwinrtcameravideorenderercontrol.h new file mode 100644 index 00000000..ed8b3388 --- /dev/null +++ b/src/plugins/winrt/qwinrtcameravideorenderercontrol.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTCAMERAVIDEORENDERERCONTROL_H +#define QWINRTCAMERAVIDEORENDERERCONTROL_H + +#include "qwinrtabstractvideorenderercontrol.h" + +struct IMF2DBuffer; + +QT_BEGIN_NAMESPACE + +class QVideoSurfaceFormat; +class QWinRTCameraVideoRendererControlPrivate; +class QWinRTCameraVideoRendererControl : public QWinRTAbstractVideoRendererControl +{ + Q_OBJECT +public: + explicit QWinRTCameraVideoRendererControl(const QSize &size, QObject *parent); + ~QWinRTCameraVideoRendererControl(); + + bool render(ID3D11Texture2D *texture) Q_DECL_OVERRIDE; + void queueBuffer(IMF2DBuffer *buffer); + void discardBuffers(); + +signals: + void bufferRequested(); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTCameraVideoRendererControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTCAMERAVIDEORENDERERCONTROL_H diff --git a/src/plugins/winrt/qwinrtserviceplugin.cpp b/src/plugins/winrt/qwinrtserviceplugin.cpp index 5d49b44a..036d63e3 100644 --- a/src/plugins/winrt/qwinrtserviceplugin.cpp +++ b/src/plugins/winrt/qwinrtserviceplugin.cpp @@ -44,6 +44,8 @@ #include "qwinrtserviceplugin.h" #include "qwinrtmediaplayerservice.h" +#include "qwinrtcameraservice.h" +#include "qwinrtvideodeviceselectorcontrol.h" QT_USE_NAMESPACE @@ -52,6 +54,9 @@ QMediaService *QWinRTServicePlugin::create(QString const &key) if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) return new QWinRTMediaPlayerService(this); + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA)) + return new QWinRTCameraService(this); + return Q_NULLPTR; } @@ -68,3 +73,37 @@ QMediaServiceProviderHint::Features QWinRTServicePlugin::supportedFeatures( return QMediaServiceProviderHint::Features(); } + +QCamera::Position QWinRTServicePlugin::cameraPosition(const QByteArray &device) const +{ + return QWinRTVideoDeviceSelectorControl::cameraPosition(device); +} + +int QWinRTServicePlugin::cameraOrientation(const QByteArray &device) const +{ + return QWinRTVideoDeviceSelectorControl::cameraOrientation(device); +} + +QList QWinRTServicePlugin::devices(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA) + return QWinRTVideoDeviceSelectorControl::deviceNames(); + + return QList(); +} + +QString QWinRTServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + if (service == Q_MEDIASERVICE_CAMERA) + return QWinRTVideoDeviceSelectorControl::deviceDescription(device); + + return QString(); +} + +QByteArray QWinRTServicePlugin::defaultDevice(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA) + return QWinRTVideoDeviceSelectorControl::defaultDeviceName(); + + return QByteArray(); +} diff --git a/src/plugins/winrt/qwinrtserviceplugin.h b/src/plugins/winrt/qwinrtserviceplugin.h index aaac79c7..9fabadb4 100644 --- a/src/plugins/winrt/qwinrtserviceplugin.h +++ b/src/plugins/winrt/qwinrtserviceplugin.h @@ -48,15 +48,29 @@ QT_USE_NAMESPACE class QWinRTServicePlugin : public QMediaServiceProviderPlugin , public QMediaServiceFeaturesInterface + , public QMediaServiceCameraInfoInterface + , public QMediaServiceSupportedDevicesInterface + , public QMediaServiceDefaultDeviceInterface { Q_OBJECT Q_INTERFACES(QMediaServiceFeaturesInterface) + Q_INTERFACES(QMediaServiceCameraInfoInterface) + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_INTERFACES(QMediaServiceDefaultDeviceInterface) Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "winrt.json") public: QMediaService *create(QString const &key); void release(QMediaService *service); QMediaServiceProviderHint::Features supportedFeatures(const QByteArray &service) const; + + QCamera::Position cameraPosition(const QByteArray &device) const Q_DECL_OVERRIDE; + int cameraOrientation(const QByteArray &device) const Q_DECL_OVERRIDE; + + QList devices(const QByteArray &service) const Q_DECL_OVERRIDE; + QString deviceDescription(const QByteArray &service, const QByteArray &device) Q_DECL_OVERRIDE; + + QByteArray defaultDevice(const QByteArray &service) const Q_DECL_OVERRIDE; }; #endif // QWINRTSERVICEPLUGIN_H diff --git a/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.cpp b/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.cpp new file mode 100644 index 00000000..8058c3da --- /dev/null +++ b/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwinrtvideodeviceselectorcontrol.h" + +#include +#include +#include +#include +#include + +#include +#include + +using namespace ABI::Windows::Devices::Enumeration; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +typedef ITypedEventHandler DeviceInformationHandler; +typedef ITypedEventHandler DeviceInformationUpdateHandler; +typedef ITypedEventHandler DeviceEnumerationCompletedHandler; + +QT_USE_NAMESPACE + +static QString deviceName(IDeviceInformation *device) +{ + HRESULT hr; + HString id; + hr = device->get_Id(id.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + quint32 length; + const wchar_t *buffer = id.GetRawBuffer(&length); + return QString::fromWCharArray(buffer, length); +} + +static QString deviceDescription(IDeviceInformation *device) +{ + HRESULT hr; + HString name; + hr = device->get_Name(name.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + quint32 length; + const wchar_t *buffer = name.GetRawBuffer(&length); + return QString::fromWCharArray(buffer, length); +} + +struct QWinRTVideoDeviceSelectorControlGlobal +{ + QWinRTVideoDeviceSelectorControlGlobal() + : defaultDeviceIndex(-1) + { + HRESULT hr; + ComPtr deviceWatcherFactory; + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Enumeration_DeviceInformation).Get(), + IID_PPV_ARGS(&deviceWatcherFactory)); + Q_ASSERT_SUCCEEDED(hr); + hr = deviceWatcherFactory->CreateWatcherDeviceClass(DeviceClass_VideoCapture, &deviceWatcher); + Q_ASSERT_SUCCEEDED(hr); + + hr = deviceWatcher->add_Added( + Callback(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceAdded).Get(), + &deviceAddedToken); + Q_ASSERT_SUCCEEDED(hr); + + hr = deviceWatcher->add_Removed( + Callback(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceRemoved).Get(), + &deviceRemovedToken); + Q_ASSERT_SUCCEEDED(hr); + + hr = deviceWatcher->add_Updated( + Callback(this, &QWinRTVideoDeviceSelectorControlGlobal::onDeviceUpdated).Get(), + &deviceUpdatedToken); + Q_ASSERT_SUCCEEDED(hr); + + // Synchronously populate the devices on construction + ComPtr> op; + hr = deviceWatcherFactory->FindAllAsyncDeviceClass(DeviceClass_VideoCapture, &op); + Q_ASSERT_SUCCEEDED(hr); + ComPtr> deviceList; + hr = QWinRTFunctions::await(op, deviceList.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + quint32 deviceCount; + hr = deviceList->get_Size(&deviceCount); + Q_ASSERT_SUCCEEDED(hr); + for (quint32 i = 0; i < deviceCount; ++i) { + IDeviceInformation *device; + hr = deviceList->GetAt(i, &device); + Q_ASSERT_SUCCEEDED(hr); + onDeviceAdded(Q_NULLPTR, device); + } + + // If there is no default device provided by the API, choose the first one + if (!devices.isEmpty() && defaultDeviceIndex < 0) + defaultDeviceIndex = 0; + } + + ~QWinRTVideoDeviceSelectorControlGlobal() + { + HRESULT hr; + hr = deviceWatcher->remove_Added(deviceAddedToken); + Q_ASSERT_SUCCEEDED(hr); + hr = deviceWatcher->remove_Removed(deviceRemovedToken); + Q_ASSERT_SUCCEEDED(hr); + hr = deviceWatcher->remove_Updated(deviceUpdatedToken); + Q_ASSERT_SUCCEEDED(hr); + } + +private: + HRESULT onDeviceAdded(IDeviceWatcher *, IDeviceInformation *device) + { + const QString name = deviceName(device); + if (deviceIndex.contains(name)) + return S_OK; + + devices.append(device); + const int index = devices.size() - 1; + deviceIndex.insert(name, index); + + HRESULT hr; + boolean isDefault; + hr = device->get_IsDefault(&isDefault); + Q_ASSERT_SUCCEEDED(hr); + if (isDefault) + defaultDeviceIndex = index; + + foreach (QWinRTVideoDeviceSelectorControl *watcher, watchers) + emit watcher->devicesChanged(); + + return S_OK; + } + + HRESULT onDeviceRemoved(IDeviceWatcher *, IDeviceInformationUpdate *device) + { + HRESULT hr; + HString id; + hr = device->get_Id(id.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + HString name; + hr = device->get_Id(name.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + quint32 nameLength; + const wchar_t *nameString = name.GetRawBuffer(&nameLength); + const int index = deviceIndex.take(QString::fromWCharArray(nameString, nameLength)); + if (index >= 0) + devices.remove(index); + + foreach (QWinRTVideoDeviceSelectorControl *watcher, watchers) + emit watcher->devicesChanged(); + + return S_OK; + } + + HRESULT onDeviceUpdated(IDeviceWatcher *, IDeviceInformationUpdate *) + { + // A name or description may have changed, so emit devicesChanged + foreach (QWinRTVideoDeviceSelectorControl *watcher, watchers) + emit watcher->devicesChanged(); + + return S_OK; + } + +public: + void addWatcher(QWinRTVideoDeviceSelectorControl *control) + { + watchers.append(control); + + HRESULT hr; + DeviceWatcherStatus status; + hr = deviceWatcher->get_Status(&status); + Q_ASSERT_SUCCEEDED(hr); + if (status != DeviceWatcherStatus_Started) { + // We can't immediately Start() if we have just called Stop() + while (status == DeviceWatcherStatus_Stopping) { + QThread::yieldCurrentThread(); + hr = deviceWatcher->get_Status(&status); + Q_ASSERT_SUCCEEDED(hr); + } + hr = deviceWatcher->Start(); + Q_ASSERT_SUCCEEDED(hr); + } + } + + void removeWatcher(QWinRTVideoDeviceSelectorControl *control) + { + watchers.removeAll(control); + + if (!watchers.isEmpty()) + return; + + HRESULT hr; + DeviceWatcherStatus status; + hr = deviceWatcher->get_Status(&status); + Q_ASSERT_SUCCEEDED(hr); + if (status == DeviceWatcherStatus_Stopped || status == DeviceWatcherStatus_Stopping) + return; + + hr = deviceWatcher->Stop(); + Q_ASSERT_SUCCEEDED(hr); + } + + QVector> devices; + QHash deviceIndex; + int defaultDeviceIndex; + +private: + ComPtr deviceWatcher; + QList watchers; + EventRegistrationToken deviceAddedToken; + EventRegistrationToken deviceRemovedToken; + EventRegistrationToken deviceUpdatedToken; +}; +Q_GLOBAL_STATIC(QWinRTVideoDeviceSelectorControlGlobal, g) + +class QWinRTVideoDeviceSelectorControlPrivate +{ +public: + int selectedDevice; +}; + +QWinRTVideoDeviceSelectorControl::QWinRTVideoDeviceSelectorControl(QObject *parent) + : QVideoDeviceSelectorControl(parent), d_ptr(new QWinRTVideoDeviceSelectorControlPrivate) +{ + Q_D(QWinRTVideoDeviceSelectorControl); + d->selectedDevice = -1; + g->addWatcher(this); +} + +QWinRTVideoDeviceSelectorControl::~QWinRTVideoDeviceSelectorControl() +{ + if (g.isDestroyed()) + return; + + g->removeWatcher(this); +} + +int QWinRTVideoDeviceSelectorControl::deviceCount() const +{ + return g->devices.size(); +} + +QString QWinRTVideoDeviceSelectorControl::deviceName(int index) const +{ + if (index < 0 || index >= g->devices.size()) + return QString(); + + return ::deviceName(g->devices.at(index).Get()); +} + +QString QWinRTVideoDeviceSelectorControl::deviceDescription(int index) const +{ + if (index < 0 || index >= g->devices.size()) + return QString(); + + return ::deviceDescription(g->devices.at(index).Get()); +} + +int QWinRTVideoDeviceSelectorControl::defaultDevice() const +{ + return g->defaultDeviceIndex; +} + +int QWinRTVideoDeviceSelectorControl::selectedDevice() const +{ + Q_D(const QWinRTVideoDeviceSelectorControl); + return d->selectedDevice; +} + +QCamera::Position QWinRTVideoDeviceSelectorControl::cameraPosition(const QString &deviceName) +{ + int deviceIndex = g->deviceIndex.value(deviceName); + IDeviceInformation *deviceInfo = g->devices.value(deviceIndex).Get(); + if (!deviceInfo) + return QCamera::UnspecifiedPosition; + + ComPtr enclosureLocation; + HRESULT hr; + hr = deviceInfo->get_EnclosureLocation(&enclosureLocation); + RETURN_IF_FAILED("Failed to get camera enclosure location", return QCamera::UnspecifiedPosition); + if (!enclosureLocation) + return QCamera::UnspecifiedPosition; + + Panel panel; + hr = enclosureLocation->get_Panel(&panel); + RETURN_IF_FAILED("Failed to get camera panel location", return QCamera::UnspecifiedPosition); + + switch (panel) { + case Panel_Front: + return QCamera::FrontFace; + case Panel_Back: + return QCamera::BackFace; + default: + break; + } + return QCamera::UnspecifiedPosition; +} + +int QWinRTVideoDeviceSelectorControl::cameraOrientation(const QString &deviceName) +{ + Q_UNUSED(deviceName); + return 0; +} + +QList QWinRTVideoDeviceSelectorControl::deviceNames() +{ + QList devices; + foreach (const QString &device, g->deviceIndex.keys()) + devices.append(device.toUtf8()); + + return devices; +} + +QByteArray QWinRTVideoDeviceSelectorControl::deviceDescription(const QByteArray &deviceName) +{ + int deviceIndex = g->deviceIndex.value(QString::fromUtf8(deviceName), -1); + if (deviceIndex < 0) + return QByteArray(); + + return ::deviceDescription(g->devices.value(deviceIndex).Get()).toUtf8(); +} + +QByteArray QWinRTVideoDeviceSelectorControl::defaultDeviceName() +{ + if (g->defaultDeviceIndex < 0) + return QByteArray(); + + return ::deviceName(g->devices.value(g->defaultDeviceIndex).Get()).toUtf8(); +} + +void QWinRTVideoDeviceSelectorControl::setSelectedDevice(int index) +{ + Q_D(QWinRTVideoDeviceSelectorControl); + + int selectedDevice = index; + if (index < 0 || index >= g->devices.size()) + selectedDevice = -1; + + if (d->selectedDevice != selectedDevice) { + d->selectedDevice = selectedDevice; + emit selectedDeviceChanged(d->selectedDevice); + emit selectedDeviceChanged(deviceName(d->selectedDevice)); + } +} diff --git a/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.h b/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.h new file mode 100644 index 00000000..2143b3ea --- /dev/null +++ b/src/plugins/winrt/qwinrtvideodeviceselectorcontrol.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINRTVIDEODEVICESELECTORCONTROL_H +#define QWINRTVIDEODEVICESELECTORCONTROL_H + +#include +#include +#include + +struct IInspectable; +namespace ABI { + namespace Windows { + namespace Devices { + namespace Enumeration { + struct IDeviceInformation; + } + } + } +} + +QT_BEGIN_NAMESPACE + +class QWinRTVideoDeviceSelectorControlPrivate; +class QWinRTVideoDeviceSelectorControl : public QVideoDeviceSelectorControl +{ + Q_OBJECT +public: + explicit QWinRTVideoDeviceSelectorControl(QObject *parent = 0); + ~QWinRTVideoDeviceSelectorControl(); + + int deviceCount() const Q_DECL_OVERRIDE; + + QString deviceName(int index) const Q_DECL_OVERRIDE; + QString deviceDescription(int index) const Q_DECL_OVERRIDE; + + int defaultDevice() const Q_DECL_OVERRIDE; + int selectedDevice() const Q_DECL_OVERRIDE; + + static QCamera::Position cameraPosition(const QString &deviceName); + static int cameraOrientation(const QString &deviceName); + static QList deviceNames(); + static QByteArray deviceDescription(const QByteArray &deviceName); + static QByteArray defaultDeviceName(); + +public slots: + void setSelectedDevice(int index) Q_DECL_OVERRIDE; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QWinRTVideoDeviceSelectorControl) +}; + +QT_END_NAMESPACE + +#endif // QWINRTVIDEODEVICESELECTORCONTROL_H diff --git a/src/plugins/winrt/winrt.json b/src/plugins/winrt/winrt.json index b85cfeb1..9af79cc3 100644 --- a/src/plugins/winrt/winrt.json +++ b/src/plugins/winrt/winrt.json @@ -1,4 +1,4 @@ { "Keys": ["winrt"], - "Services": ["org.qt-project.qt.mediaplayer"] + "Services": ["org.qt-project.qt.mediaplayer", "org.qt-project.qt.camera"] } diff --git a/src/plugins/winrt/winrt.pro b/src/plugins/winrt/winrt.pro index 0ea90d22..04db71e7 100644 --- a/src/plugins/winrt/winrt.pro +++ b/src/plugins/winrt/winrt.pro @@ -5,21 +5,33 @@ PLUGIN_TYPE=mediaservice PLUGIN_CLASS_NAME = WinRTServicePlugin load(qt_plugin) -LIBS += -lmfplat -lmfuuid -loleaut32 -ld3d11 +LIBS += -lmfplat -lmfuuid -loleaut32 -ld3d11 -lruntimeobject HEADERS += \ qwinrtabstractvideorenderercontrol.h \ + qwinrtcameracontrol.h \ + qwinrtcamerainfocontrol.h \ + qwinrtcameraimagecapturecontrol.h \ + qwinrtcameraservice.h \ + qwinrtcameravideorenderercontrol.h \ qwinrtmediaplayercontrol.h \ qwinrtmediaplayerservice.h \ qwinrtplayerrenderercontrol.h \ - qwinrtserviceplugin.h + qwinrtserviceplugin.h \ + qwinrtvideodeviceselectorcontrol.h SOURCES += \ qwinrtabstractvideorenderercontrol.cpp \ + qwinrtcameracontrol.cpp \ + qwinrtcamerainfocontrol.cpp \ + qwinrtcameraimagecapturecontrol.cpp \ + qwinrtcameraservice.cpp \ + qwinrtcameravideorenderercontrol.cpp \ qwinrtmediaplayercontrol.cpp \ qwinrtmediaplayerservice.cpp \ qwinrtplayerrenderercontrol.cpp \ - qwinrtserviceplugin.cpp + qwinrtserviceplugin.cpp \ + qwinrtvideodeviceselectorcontrol.cpp OTHER_FILES += \ winrt.json From 006cdeee92b0ecc292cfba7871fd1dafaff4c05d Mon Sep 17 00:00:00 2001 From: Bernd Weimer Date: Wed, 3 Sep 2014 13:46:36 +0200 Subject: [PATCH 15/48] QNX: Fix end of media notification When auto-play is on, EndOfMedia would not be emitted. This is due to a workaround for mmrenderer, that wrongly ignored stop events. Once media is played stop events will always have to be processed. Change-Id: I1cfd665bb06638ee3c86807aecc51e78f9baa938 Reviewed-by: Rafael Roquetto --- src/plugins/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp b/src/plugins/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp index abb68278..66d64935 100644 --- a/src/plugins/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp +++ b/src/plugins/qnx/mediaplayer/mmrenderermediaplayercontrol.cpp @@ -546,6 +546,7 @@ void MmRendererMediaPlayerControl::play() return; } + m_stopEventsToIgnore = 0; // once playing, stop events must be proccessed setState( QMediaPlayer::PlayingState); } From d00ad751bd1a7efff43e951955cc6b46ecbe5d73 Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Tue, 9 Sep 2014 11:48:46 +0200 Subject: [PATCH 16/48] ANGLE is never available on WinCE Change-Id: I853774af533d7f4b37b4789344e531d2688f91f5 Reviewed-by: Alex Blasche --- src/multimedia/qmediaopenglhelper_p.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/multimedia/qmediaopenglhelper_p.h b/src/multimedia/qmediaopenglhelper_p.h index d42dfba4..0dbd79d4 100644 --- a/src/multimedia/qmediaopenglhelper_p.h +++ b/src/multimedia/qmediaopenglhelper_p.h @@ -66,7 +66,7 @@ inline bool QMediaOpenGLHelper::isANGLE() #else bool isANGLE = false; -# if defined(Q_OS_WIN) && (defined(QT_OPENGL_ES_2) || defined(QT_OPENGL_DYNAMIC)) +# if defined(Q_OS_WIN) && !defined(Q_OS_WINCE) && (defined(QT_OPENGL_ES_2) || defined(QT_OPENGL_DYNAMIC)) if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { // Although unlikely, technically LibGLES could mean a non-ANGLE EGL/GLES2 implementation too. // Verify that it is indeed ANGLE. @@ -98,7 +98,7 @@ inline bool QMediaOpenGLHelper::isANGLE() # endif // QT_OPENGL_ES_2_ANGLE_STATIC } -# endif // Q_OS_WIN && (QT_OPENGL_ES_2 || QT_OPENGL_DYNAMIC) +# endif // Q_OS_WIN && !Q_OS_WINCE && (QT_OPENGL_ES_2 || QT_OPENGL_DYNAMIC) return isANGLE; #endif // Q_OS_WINRT From 18d6560db15d8f65b221717b9a769fd0f00cec35 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 3 Sep 2014 14:45:50 +0200 Subject: [PATCH 17/48] Fix integer overflow in several audio plugins. Task-number: QTBUG-40804 Change-Id: If006cb7db319bb6fda4ce7eb4f907e897b5d9efa Reviewed-by: Christian Stromme --- src/plugins/alsa/qalsaaudioinput.cpp | 2 +- src/plugins/alsa/qalsaaudiooutput.cpp | 2 +- src/plugins/opensles/qopenslesaudioinput.cpp | 2 +- src/plugins/opensles/qopenslesaudiooutput.cpp | 2 +- src/plugins/pulseaudio/qaudioinput_pulse.cpp | 2 +- src/plugins/pulseaudio/qaudiooutput_pulse.cpp | 2 +- src/plugins/qnx-audio/audio/qnxaudioinput.cpp | 2 +- src/plugins/qnx-audio/audio/qnxaudiooutput.cpp | 2 +- src/plugins/windowsaudio/qwindowsaudioinput.cpp | 2 +- src/plugins/windowsaudio/qwindowsaudiooutput.cpp | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/plugins/alsa/qalsaaudioinput.cpp b/src/plugins/alsa/qalsaaudioinput.cpp index e01f2d70..c66fdb7f 100644 --- a/src/plugins/alsa/qalsaaudioinput.cpp +++ b/src/plugins/alsa/qalsaaudioinput.cpp @@ -768,7 +768,7 @@ qint64 QAlsaAudioInput::elapsedUSecs() const if (deviceState == QAudio::StoppedState) return 0; - return clockStamp.elapsed()*1000; + return clockStamp.elapsed() * qint64(1000); } void QAlsaAudioInput::reset() diff --git a/src/plugins/alsa/qalsaaudiooutput.cpp b/src/plugins/alsa/qalsaaudiooutput.cpp index c8d709cf..da25a885 100644 --- a/src/plugins/alsa/qalsaaudiooutput.cpp +++ b/src/plugins/alsa/qalsaaudiooutput.cpp @@ -782,7 +782,7 @@ qint64 QAlsaAudioOutput::elapsedUSecs() const if (deviceState == QAudio::StoppedState) return 0; - return clockStamp.elapsed()*1000; + return clockStamp.elapsed() * qint64(1000); } void QAlsaAudioOutput::reset() diff --git a/src/plugins/opensles/qopenslesaudioinput.cpp b/src/plugins/opensles/qopenslesaudioinput.cpp index ac980188..98ddea73 100644 --- a/src/plugins/opensles/qopenslesaudioinput.cpp +++ b/src/plugins/opensles/qopenslesaudioinput.cpp @@ -482,7 +482,7 @@ qint64 QOpenSLESAudioInput::elapsedUSecs() const if (m_deviceState == QAudio::StoppedState) return 0; - return m_clockStamp.elapsed() * 1000; + return m_clockStamp.elapsed() * qint64(1000); } void QOpenSLESAudioInput::setVolume(qreal vol) diff --git a/src/plugins/opensles/qopenslesaudiooutput.cpp b/src/plugins/opensles/qopenslesaudiooutput.cpp index 9c62852d..5457abaf 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.cpp +++ b/src/plugins/opensles/qopenslesaudiooutput.cpp @@ -290,7 +290,7 @@ qint64 QOpenSLESAudioOutput::elapsedUSecs() const if (m_state == QAudio::StoppedState) return 0; - return m_clockStamp.elapsed() * 1000; + return m_clockStamp.elapsed() * qint64(1000); } void QOpenSLESAudioOutput::reset() diff --git a/src/plugins/pulseaudio/qaudioinput_pulse.cpp b/src/plugins/pulseaudio/qaudioinput_pulse.cpp index 89dc0861..b34d7542 100644 --- a/src/plugins/pulseaudio/qaudioinput_pulse.cpp +++ b/src/plugins/pulseaudio/qaudioinput_pulse.cpp @@ -692,7 +692,7 @@ qint64 QPulseAudioInput::elapsedUSecs() const if (m_deviceState == QAudio::StoppedState) return 0; - return m_clockStamp.elapsed() * 1000; + return m_clockStamp.elapsed() * qint64(1000); } void QPulseAudioInput::reset() diff --git a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp b/src/plugins/pulseaudio/qaudiooutput_pulse.cpp index 64a08066..b2046330 100644 --- a/src/plugins/pulseaudio/qaudiooutput_pulse.cpp +++ b/src/plugins/pulseaudio/qaudiooutput_pulse.cpp @@ -591,7 +591,7 @@ qint64 QPulseAudioOutput::elapsedUSecs() const if (m_deviceState == QAudio::StoppedState) return 0; - return m_clockStamp.elapsed() * 1000; + return m_clockStamp.elapsed() * qint64(1000); } void QPulseAudioOutput::reset() diff --git a/src/plugins/qnx-audio/audio/qnxaudioinput.cpp b/src/plugins/qnx-audio/audio/qnxaudioinput.cpp index eb806459..2c5956bd 100644 --- a/src/plugins/qnx-audio/audio/qnxaudioinput.cpp +++ b/src/plugins/qnx-audio/audio/qnxaudioinput.cpp @@ -194,7 +194,7 @@ qint64 QnxAudioInput::elapsedUSecs() const if (m_state == QAudio::StoppedState) return 0; - return m_clockStamp.elapsed() * 1000; + return m_clockStamp.elapsed() * qint64(1000); } QAudio::Error QnxAudioInput::error() const diff --git a/src/plugins/qnx-audio/audio/qnxaudiooutput.cpp b/src/plugins/qnx-audio/audio/qnxaudiooutput.cpp index 4a82e93b..c12f3abb 100644 --- a/src/plugins/qnx-audio/audio/qnxaudiooutput.cpp +++ b/src/plugins/qnx-audio/audio/qnxaudiooutput.cpp @@ -180,7 +180,7 @@ qint64 QnxAudioOutput::elapsedUSecs() const if (m_state == QAudio::StoppedState) return 0; else - return m_startTimeStamp.elapsed() * 1000; + return m_startTimeStamp.elapsed() * qint64(1000); } QAudio::Error QnxAudioOutput::error() const diff --git a/src/plugins/windowsaudio/qwindowsaudioinput.cpp b/src/plugins/windowsaudio/qwindowsaudioinput.cpp index 26f0641b..e55f3ab6 100644 --- a/src/plugins/windowsaudio/qwindowsaudioinput.cpp +++ b/src/plugins/windowsaudio/qwindowsaudioinput.cpp @@ -706,7 +706,7 @@ qint64 QWindowsAudioInput::elapsedUSecs() const if (deviceState == QAudio::StoppedState) return 0; - return timeStampOpened.elapsed()*1000; + return timeStampOpened.elapsed() * qint64(1000); } void QWindowsAudioInput::reset() diff --git a/src/plugins/windowsaudio/qwindowsaudiooutput.cpp b/src/plugins/windowsaudio/qwindowsaudiooutput.cpp index 1c8882ee..360441ad 100644 --- a/src/plugins/windowsaudio/qwindowsaudiooutput.cpp +++ b/src/plugins/windowsaudio/qwindowsaudiooutput.cpp @@ -682,7 +682,7 @@ qint64 QWindowsAudioOutput::elapsedUSecs() const if (deviceState == QAudio::StoppedState) return 0; - return timeStampOpened.elapsed()*1000; + return timeStampOpened.elapsed() * qint64(1000); } QAudio::Error QWindowsAudioOutput::error() const From 973ae5e0f60d335d1b22f9a2418fe50839d50322 Mon Sep 17 00:00:00 2001 From: Andres Gomez Date: Wed, 30 Jul 2014 17:34:16 +0300 Subject: [PATCH 18/48] QMediaNetworkPlaylistProvider: Upon error parsing, stop parsing. When an error is found parsing a playlist, stop parsing. This will also prevent the emission of the "loaded" signal when the parser finishes. Task-number: QTBUG-40513 Change-Id: Ia814864d0d546806219993f0b727761d5d4e7903 Reviewed-by: Yoann Lopes --- .../qmedianetworkplaylistprovider.cpp | 2 + .../unit/qmediaplaylist/testdata/test.pls | 10 +++ .../testdata/totem-pl-example.pls | 5 ++ .../unit/qmediaplaylist/testdata/trash.pls | 2 + .../qmediaplaylist/tst_qmediaplaylist.cpp | 75 ++++++++++++++++++- 5 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tests/auto/unit/qmediaplaylist/testdata/test.pls create mode 100644 tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls create mode 100644 tests/auto/unit/qmediaplaylist/testdata/trash.pls diff --git a/src/multimedia/playback/qmedianetworkplaylistprovider.cpp b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp index a8d8c4b0..9bbd7f3a 100644 --- a/src/multimedia/playback/qmedianetworkplaylistprovider.cpp +++ b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp @@ -90,6 +90,8 @@ void QMediaNetworkPlaylistProviderPrivate::_q_handleParserError(QPlaylistFilePar break; } + parser.stop(); + emit q->loadFailed(playlistError, errorMessage); } diff --git a/tests/auto/unit/qmediaplaylist/testdata/test.pls b/tests/auto/unit/qmediaplaylist/testdata/test.pls new file mode 100644 index 00000000..1b66c3ab --- /dev/null +++ b/tests/auto/unit/qmediaplaylist/testdata/test.pls @@ -0,0 +1,10 @@ +[playlist] + +File1=http://test.host/path +Title1=First +Length1=-1 +File2= http://test.host/path +Title2=Second +Length2=-1 + +NumberOfEntries=2 diff --git a/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls b/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls new file mode 100644 index 00000000..385fe2a3 --- /dev/null +++ b/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls @@ -0,0 +1,5 @@ +[playlist] +X-GNOME-Title=totem-pl-file-example +NumberOfEntries=1 +File1=http://test.host/path +Title1=Silence diff --git a/tests/auto/unit/qmediaplaylist/testdata/trash.pls b/tests/auto/unit/qmediaplaylist/testdata/trash.pls new file mode 100644 index 00000000..639c22b0 --- /dev/null +++ b/tests/auto/unit/qmediaplaylist/testdata/trash.pls @@ -0,0 +1,2 @@ +[playlist] +NumberOfEntries=100 diff --git a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp index 7aa8d7a5..63b84b5e 100644 --- a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp +++ b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp @@ -86,6 +86,7 @@ private slots: void currentItem(); void saveAndLoad(); void loadM3uFile(); + void loadPLSFile(); void playbackMode(); void playbackMode_data(); void shuffle(); @@ -356,8 +357,10 @@ void tst_QMediaPlaylist::saveAndLoad() QVERIFY(playlist.error() == QMediaPlaylist::FormatNotSupportedError); QVERIFY(!playlist.errorString().isEmpty()); + QSignalSpy loadedSignal(&playlist, SIGNAL(loaded())); QSignalSpy errorSignal(&playlist, SIGNAL(loadFailed())); playlist.load(&buffer, "unsupported_format"); + QTRY_VERIFY(loadedSignal.isEmpty()); QCOMPARE(errorSignal.size(), 1); QVERIFY(playlist.error() != QMediaPlaylist::NoError); QVERIFY(!playlist.errorString().isEmpty()); @@ -367,8 +370,10 @@ void tst_QMediaPlaylist::saveAndLoad() QVERIFY(playlist.error() != QMediaPlaylist::NoError); QVERIFY(!playlist.errorString().isEmpty()); + loadedSignal.clear(); errorSignal.clear(); playlist.load(QUrl::fromLocalFile(QLatin1String("tmp.unsupported_format")), "unsupported_format"); + QTRY_VERIFY(loadedSignal.isEmpty()); QCOMPARE(errorSignal.size(), 1); QVERIFY(playlist.error() == QMediaPlaylist::FormatNotSupportedError); QVERIFY(!playlist.errorString().isEmpty()); @@ -380,7 +385,11 @@ void tst_QMediaPlaylist::saveAndLoad() buffer.seek(0); QMediaPlaylist playlist2; + QSignalSpy loadedSignal2(&playlist2, SIGNAL(loaded())); + QSignalSpy errorSignal2(&playlist2, SIGNAL(loadFailed())); playlist2.load(&buffer, "m3u"); + QCOMPARE(loadedSignal2.size(), 1); + QTRY_VERIFY(errorSignal2.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), playlist2.mediaCount()); @@ -390,9 +399,13 @@ void tst_QMediaPlaylist::saveAndLoad() res = playlist.save(QUrl::fromLocalFile(QLatin1String("tmp.m3u")), "m3u"); QVERIFY(res); + loadedSignal2.clear(); + errorSignal2.clear(); playlist2.clear(); QVERIFY(playlist2.isEmpty()); playlist2.load(QUrl::fromLocalFile(QLatin1String("tmp.m3u")), "m3u"); + QCOMPARE(loadedSignal2.size(), 1); + QTRY_VERIFY(errorSignal2.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), playlist2.mediaCount()); @@ -406,12 +419,20 @@ void tst_QMediaPlaylist::loadM3uFile() QMediaPlaylist playlist; // Try to load playlist that does not exist in the testdata folder + QSignalSpy loadSpy(&playlist, SIGNAL(loaded())); + QSignalSpy loadFailedSpy(&playlist, SIGNAL(loadFailed())); QString testFileName = QFINDTESTDATA("testdata"); playlist.load(QUrl::fromLocalFile(testFileName + "/missing_file.m3u")); + QTRY_VERIFY(loadSpy.isEmpty()); + QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(playlist.error() != QMediaPlaylist::NoError); + loadSpy.clear(); + loadFailedSpy.clear(); testFileName = QFINDTESTDATA("testdata/test.m3u"); playlist.load(QUrl::fromLocalFile(testFileName)); + QTRY_VERIFY(!loadSpy.isEmpty()); + QVERIFY(loadFailedSpy.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), 7); @@ -428,10 +449,62 @@ void tst_QMediaPlaylist::loadM3uFile() //ensure #2 suffix is not stripped from path testFileName = QFINDTESTDATA("testdata/testfile2#suffix"); QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); + // check ability to load from QNetworkRequest + loadSpy.clear(); + loadFailedSpy.clear(); + playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.m3u")))); + QTRY_VERIFY(!loadSpy.isEmpty()); + QVERIFY(loadFailedSpy.isEmpty()); +} + +void tst_QMediaPlaylist::loadPLSFile() +{ + QMediaPlaylist playlist; + + // Try to load playlist that does not exist in the testdata folder QSignalSpy loadSpy(&playlist, SIGNAL(loaded())); QSignalSpy loadFailedSpy(&playlist, SIGNAL(loadFailed())); - playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.m3u")))); + QString testFileName = QFINDTESTDATA("testdata"); + playlist.load(QUrl::fromLocalFile(testFileName + "/missing_file.pls")); + QTRY_VERIFY(loadSpy.isEmpty()); + QVERIFY(!loadFailedSpy.isEmpty()); + QVERIFY(playlist.error() != QMediaPlaylist::NoError); + + // Try to load bogus playlist + loadSpy.clear(); + loadFailedSpy.clear(); + testFileName = QFINDTESTDATA("testdata/trash.pls"); + playlist.load(QUrl::fromLocalFile(testFileName)); + QTRY_VERIFY(loadSpy.isEmpty()); + QVERIFY(!loadFailedSpy.isEmpty()); + QVERIFY(playlist.error() == QMediaPlaylist::FormatError); + + // Try to load regular playlist + loadSpy.clear(); + loadFailedSpy.clear(); + testFileName = QFINDTESTDATA("testdata/test.pls"); + playlist.load(QUrl::fromLocalFile(testFileName)); + QTRY_VERIFY(!loadSpy.isEmpty()); + QVERIFY(loadFailedSpy.isEmpty()); + QCOMPARE(playlist.error(), QMediaPlaylist::NoError); + QCOMPARE(playlist.mediaCount(), 2); + QCOMPARE(playlist.media(0).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); + QCOMPARE(playlist.media(1).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); + + // Try to load a totem-pl generated playlist + loadSpy.clear(); + loadFailedSpy.clear(); + testFileName = QFINDTESTDATA("testdata/totem-pl-example.pls"); + playlist.load(QUrl::fromLocalFile(testFileName)); + QTRY_VERIFY(loadSpy.isEmpty()); + QVERIFY(!loadFailedSpy.isEmpty()); + QVERIFY(playlist.error() == QMediaPlaylist::FormatError); + + // check ability to load from QNetworkRequest + loadSpy.clear(); + loadFailedSpy.clear(); + playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.pls")))); QTRY_VERIFY(!loadSpy.isEmpty()); QVERIFY(loadFailedSpy.isEmpty()); } From f803f87068658a2b4bf4ecd137229d084ab87ad4 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 8 Sep 2014 17:07:51 +0200 Subject: [PATCH 19/48] Fix build with QT_OPENGL_DYNAMIC Change-Id: I4375a808170f3e4888101fea7a833399061a8935 Reviewed-by: Laszlo Agocs --- src/plugins/wmf/evrd3dpresentengine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/wmf/evrd3dpresentengine.cpp b/src/plugins/wmf/evrd3dpresentengine.cpp index a1b63321..bc27a7e0 100644 --- a/src/plugins/wmf/evrd3dpresentengine.cpp +++ b/src/plugins/wmf/evrd3dpresentengine.cpp @@ -48,7 +48,6 @@ #include #include -#include #include #include #include From f65cf958b1a40d486854a1c2526d20e5a50a944b Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 9 Sep 2014 14:13:20 +0200 Subject: [PATCH 20/48] Fix V4L usage. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ff527de0 was submitted to 5.3 while cddbe873 was submitted to 5.4. The former's behavior was not preserved when 5.3 got merged into 5.4. Change-Id: I7435ea30634001ae6e87c316eb8a8ab6f5e988e3 Reviewed-by: Lisandro Damián Nicanor Pérez Meyer Reviewed-by: Andrew den Exter --- src/gsttools/qgstutils.cpp | 8 ++++++-- src/plugins/gstreamer/camerabin/camerabin.pro | 2 -- .../gstreamer/camerabin/camerabinserviceplugin.cpp | 7 ------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/gsttools/qgstutils.cpp b/src/gsttools/qgstutils.cpp index 9740b9a0..8d484aa6 100644 --- a/src/gsttools/qgstutils.cpp +++ b/src/gsttools/qgstutils.cpp @@ -42,8 +42,10 @@ #include #include -#include -#include +#ifdef USE_V4L +# include +# include +#endif #include "qgstreamervideoinputdevicecontrol_p.h" @@ -469,6 +471,7 @@ QVector QGstUtils::enumerateCameras(GstElementFactory *fa } } +#ifdef USE_V4L QDir devDir(QStringLiteral("/dev")); devDir.setFilter(QDir::System); @@ -516,6 +519,7 @@ QVector QGstUtils::enumerateCameras(GstElementFactory *fa } qt_safe_close(fd); } +#endif // USE_V4L return devices; } diff --git a/src/plugins/gstreamer/camerabin/camerabin.pro b/src/plugins/gstreamer/camerabin/camerabin.pro index 90a1040f..bba797f5 100644 --- a/src/plugins/gstreamer/camerabin/camerabin.pro +++ b/src/plugins/gstreamer/camerabin/camerabin.pro @@ -83,8 +83,6 @@ config_gstreamer_photography { DEFINES += GST_USE_UNSTABLE_API #prevents warnings because of unstable photography API } -config_linux_v4l: DEFINES += USE_V4L - OTHER_FILES += \ camerabin.json diff --git a/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp b/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp index 8c943529..51024b7d 100644 --- a/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinserviceplugin.cpp @@ -38,16 +38,9 @@ #include "camerabinserviceplugin.h" - #include "camerabinservice.h" #include -#include - -#if defined(USE_V4L) -#include -#endif - QT_BEGIN_NAMESPACE template static int lengthOf(const T(&)[N]) { return N; } From e26483c106cd1408b768f18f5d0edfd83c78f5bf Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 27 Aug 2014 16:25:40 +0200 Subject: [PATCH 21/48] Fix URL handling in PLS parser. Make sure relative paths are resolved to a full path. Task-number: QTBUG-40515 Change-Id: Ideb83fc3a3c4a74c84917a22e3c30162d7b6158a Reviewed-by: Christian Stromme --- .../playback/playlistfileparser.cpp | 51 ++++++++++--------- .../unit/qmediaplaylist/testdata/test.pls | 19 ++++++- .../qmediaplaylist/tst_qmediaplaylist.cpp | 14 ++++- 3 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/multimedia/playback/playlistfileparser.cpp b/src/multimedia/playback/playlistfileparser.cpp index 1254d613..97c551c9 100644 --- a/src/multimedia/playback/playlistfileparser.cpp +++ b/src/multimedia/playback/playlistfileparser.cpp @@ -59,6 +59,30 @@ public: virtual void parseLine(int lineIndex, const QString& line, const QUrl& root) = 0; +protected: + QUrl expandToFullPath(const QUrl &root, const QString &line) + { + // On Linux, backslashes are not converted to forward slashes :/ + if (line.startsWith(QLatin1String("//")) || line.startsWith(QLatin1String("\\\\"))) { + // Network share paths are not resolved + return QUrl::fromLocalFile(line); + } + + QUrl url(line); + if (url.scheme().isEmpty()) { + // Resolve it relative to root + if (root.isLocalFile()) + return root.resolved(QUrl::fromLocalFile(line)); + else + return root.resolved(url); + } else if (url.scheme().length() == 1) { + // Assume it's a drive letter for a Windows path + url = QUrl::fromLocalFile(line); + } + + return url; + } + Q_SIGNALS: void newItem(const QVariant& content); void finished(); @@ -146,29 +170,6 @@ public: return -1; } - QUrl expandToFullPath(const QUrl& root, const QString& line) - { - // On Linux, backslashes are not converted to forward slashes :/ - if (line.startsWith(QLatin1String("//")) || line.startsWith(QLatin1String("\\\\"))) { - // Network share paths are not resolved - return QUrl::fromLocalFile(line); - } - - QUrl url(line); - if (url.scheme().isEmpty()) { - // Resolve it relative to root - if (root.isLocalFile()) - return root.resolved(QUrl::fromLocalFile(line)); - else - return root.resolved(url); - } else if (url.scheme().length() == 1) { - // Assume it's a drive letter for a Windows path - url = QUrl::fromLocalFile(line); - } - - return url; - } - private: bool m_extendedFormat; QVariantMap m_extraInfo; @@ -249,7 +250,7 @@ Version=2 m_readFlags |= int(flag); } - void parseLine(int lineIndex, const QString& line, const QUrl&) + void parseLine(int lineIndex, const QString &line, const QUrl &root) { switch (m_state) { case Header: @@ -260,7 +261,7 @@ Version=2 break; case Track: if (!containsFlag(FileRead) && line.startsWith(m_fileName)) { - m_item[QLatin1String("url")] = getValue(lineIndex, line); + m_item[QLatin1String("url")] = expandToFullPath(root, getValue(lineIndex, line)); setFlag(FileRead); } else if (!containsFlag(TitleRead) && line.startsWith(m_titleName)) { m_item[QMediaMetaData::Title] = getValue(lineIndex, line); diff --git a/tests/auto/unit/qmediaplaylist/testdata/test.pls b/tests/auto/unit/qmediaplaylist/testdata/test.pls index 1b66c3ab..18832b10 100644 --- a/tests/auto/unit/qmediaplaylist/testdata/test.pls +++ b/tests/auto/unit/qmediaplaylist/testdata/test.pls @@ -6,5 +6,22 @@ Length1=-1 File2= http://test.host/path Title2=Second Length2=-1 +File3=testfile +Title3=Third +Length3=-1 -NumberOfEntries=2 + + +File4=testdir/testfile +Title4=Fourth +Length4=-1 +File5=/testdir/testfile +Title5=Fifth +Length5=-1 +File6=file://path/name#suffix +Title6=Sixth +Length6=-1 +File7=testfile2#suffix +Title7=Seventh +Length7=-1 +NumberOfEntries=7 diff --git a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp index 63b84b5e..748bcd30 100644 --- a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp +++ b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp @@ -488,9 +488,21 @@ void tst_QMediaPlaylist::loadPLSFile() QTRY_VERIFY(!loadSpy.isEmpty()); QVERIFY(loadFailedSpy.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); - QCOMPARE(playlist.mediaCount(), 2); + QCOMPARE(playlist.mediaCount(), 7); + QCOMPARE(playlist.media(0).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); QCOMPARE(playlist.media(1).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); + testFileName = QFINDTESTDATA("testdata/testfile"); + QCOMPARE(playlist.media(2).canonicalUrl(), + QUrl::fromLocalFile(testFileName)); + testFileName = QFINDTESTDATA("testdata"); + QCOMPARE(playlist.media(3).canonicalUrl(), + QUrl::fromLocalFile(testFileName + "/testdir/testfile")); + QCOMPARE(playlist.media(4).canonicalUrl(), QUrl(QLatin1String("file:///testdir/testfile"))); + QCOMPARE(playlist.media(5).canonicalUrl(), QUrl(QLatin1String("file://path/name#suffix"))); + //ensure #2 suffix is not stripped from path + testFileName = QFINDTESTDATA("testdata/testfile2#suffix"); + QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); // Try to load a totem-pl generated playlist loadSpy.clear(); From 90fd3ac39999389fd898dd43210f8af95adb5493 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 9 Sep 2014 14:59:06 +0200 Subject: [PATCH 22/48] WMF: fix start time of QAudioProbe's buffers. Task-number: QTBUG-40954 Change-Id: Icd1d144dcff3a3191432722da44a263ca286dbb6 Reviewed-by: Christian Stromme --- src/plugins/wmf/samplegrabber.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/plugins/wmf/samplegrabber.cpp b/src/plugins/wmf/samplegrabber.cpp index e4ead539..41ff8d79 100644 --- a/src/plugins/wmf/samplegrabber.cpp +++ b/src/plugins/wmf/samplegrabber.cpp @@ -163,6 +163,9 @@ STDMETHODIMP AudioSampleGrabberCallback::OnProcessSample(REFGUID guidMajorMediaT if (llSampleTime == _I64_MAX) { // Set default QAudioBuffer start time llSampleTime = -1; + } else { + // WMF uses 100-nanosecond units, Qt uses microseconds + llSampleTime /= 10; } foreach (MFAudioProbeControl* probe, m_audioProbes) From 4c5aec9bb6fd95a65544aa433f1357320132ae9f Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 28 Aug 2014 16:00:15 +0200 Subject: [PATCH 23/48] Make PLS parser more permissive. The PLS format is not clearly specified, some rules are just assumed and files don't always respect them. We now only look for 'File' entries, since that's the only thing we actually use. We ignore the Version, NumberOfEntries, Title, Length and any other unrecognized tags. Task-number: QTBUG-40515 Change-Id: I9c176b7b68fd1441abbd50364f88994ad5d6236f Reviewed-by: Christian Stromme --- .../playback/playlistfileparser.cpp | 106 ++---------------- .../testdata/{trash.pls => empty.pls} | 0 .../qmediaplaylist/tst_qmediaplaylist.cpp | 22 ++-- 3 files changed, 26 insertions(+), 102 deletions(-) rename tests/auto/unit/qmediaplaylist/testdata/{trash.pls => empty.pls} (100%) diff --git a/src/multimedia/playback/playlistfileparser.cpp b/src/multimedia/playback/playlistfileparser.cpp index 97c551c9..374d3fa0 100644 --- a/src/multimedia/playback/playlistfileparser.cpp +++ b/src/multimedia/playback/playlistfileparser.cpp @@ -181,27 +181,9 @@ class PLSParser : public ParserBase public: PLSParser(QObject *parent) : ParserBase(parent) - , m_state(Header) - , m_count(0) - , m_readFlags(0) { } - enum ReadFlags - { - FileRead = 0x1, - TitleRead = 0x2, - LengthRead = 0x4, - All = FileRead | TitleRead | LengthRead - }; - - enum State - { - Header, - Track, - Footer - }; - /* * The format is essentially that of an INI file structured as follows: @@ -240,89 +222,25 @@ NumberOfEntries=2 Version=2 */ - inline bool containsFlag(const ReadFlags& flag) + void parseLine(int, const QString &line, const QUrl &root) { - return (m_readFlags & int(flag)) == flag; + // We ignore everything but 'File' entries, since that's the only thing we care about. + if (!line.startsWith(QLatin1String("File"))) + return; + + QString value = getValue(line); + if (value.isEmpty()) + return; + + emit newItem(expandToFullPath(root, value)); } - inline void setFlag(const ReadFlags& flag) - { - m_readFlags |= int(flag); - } - - void parseLine(int lineIndex, const QString &line, const QUrl &root) - { - switch (m_state) { - case Header: - if (line == QLatin1String("[playlist]")) { - m_state = Track; - setCount(1); - } - break; - case Track: - if (!containsFlag(FileRead) && line.startsWith(m_fileName)) { - m_item[QLatin1String("url")] = expandToFullPath(root, getValue(lineIndex, line)); - setFlag(FileRead); - } else if (!containsFlag(TitleRead) && line.startsWith(m_titleName)) { - m_item[QMediaMetaData::Title] = getValue(lineIndex, line); - setFlag(TitleRead); - } else if (!containsFlag(LengthRead) && line.startsWith(m_lengthName)) { - //convert from seconds to miliseconds - int length = getValue(lineIndex, line).toInt(); - if (length > 0) - m_item[QMediaMetaData::Duration] = length * 1000; - setFlag(LengthRead); - } else if (line.startsWith(QLatin1String("NumberOfEntries"))) { - m_state = Footer; - int entries = getValue(lineIndex, line).toInt(); - int count = m_readFlags == 0 ? (m_count - 1) : m_count; - if (entries != count) { - emit error(QPlaylistFileParser::FormatError, tr("Error parsing playlist: %1, expected count = %2"). - arg(line, QString::number(count))); - } - break; - } - if (m_readFlags == int(All)) { - emit newItem(m_item); - setCount(m_count + 1); - } - break; - case Footer: - if (line.startsWith(QLatin1String("Version"))) { - int version = getValue(lineIndex, line).toInt(); - if (version != 2) - emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing playlist at line[%1], expected version = 2")).arg(line)); - } - break; - } - } - - QString getValue(int lineIndex, const QString& line) { + QString getValue(const QString& line) { int start = line.indexOf('='); - if (start < 0) { - emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing playlist at line[%1]:%2")).arg(QString::number(lineIndex), line)); + if (start < 0) return QString(); - } return line.midRef(start + 1).trimmed().toString(); } - - void setCount(int count) { - m_count = count; - m_fileName = QStringLiteral("File%1").arg(count); - m_titleName = QStringLiteral("Title%1").arg(count); - m_lengthName = QStringLiteral("Length%1").arg(count); - m_item.clear(); - m_readFlags = 0; - } - -private: - State m_state; - int m_count; - QString m_titleName; - QString m_fileName; - QString m_lengthName; - QVariantMap m_item; - int m_readFlags; }; } diff --git a/tests/auto/unit/qmediaplaylist/testdata/trash.pls b/tests/auto/unit/qmediaplaylist/testdata/empty.pls similarity index 100% rename from tests/auto/unit/qmediaplaylist/testdata/trash.pls rename to tests/auto/unit/qmediaplaylist/testdata/empty.pls diff --git a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp index 748bcd30..82b7bdaf 100644 --- a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp +++ b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp @@ -471,14 +471,15 @@ void tst_QMediaPlaylist::loadPLSFile() QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(playlist.error() != QMediaPlaylist::NoError); - // Try to load bogus playlist + // Try to load empty playlist loadSpy.clear(); loadFailedSpy.clear(); - testFileName = QFINDTESTDATA("testdata/trash.pls"); + testFileName = QFINDTESTDATA("testdata/empty.pls"); playlist.load(QUrl::fromLocalFile(testFileName)); - QTRY_VERIFY(loadSpy.isEmpty()); - QVERIFY(!loadFailedSpy.isEmpty()); - QVERIFY(playlist.error() == QMediaPlaylist::FormatError); + QTRY_VERIFY(!loadSpy.isEmpty()); + QVERIFY(loadFailedSpy.isEmpty()); + QCOMPARE(playlist.error(), QMediaPlaylist::NoError); + QCOMPARE(playlist.mediaCount(), 0); // Try to load regular playlist loadSpy.clear(); @@ -505,13 +506,18 @@ void tst_QMediaPlaylist::loadPLSFile() QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); // Try to load a totem-pl generated playlist + // (Format doesn't respect the spec) loadSpy.clear(); loadFailedSpy.clear(); + playlist.clear(); testFileName = QFINDTESTDATA("testdata/totem-pl-example.pls"); playlist.load(QUrl::fromLocalFile(testFileName)); - QTRY_VERIFY(loadSpy.isEmpty()); - QVERIFY(!loadFailedSpy.isEmpty()); - QVERIFY(playlist.error() == QMediaPlaylist::FormatError); + QTRY_VERIFY(!loadSpy.isEmpty()); + QVERIFY(loadFailedSpy.isEmpty()); + QCOMPARE(playlist.error(), QMediaPlaylist::NoError); + QCOMPARE(playlist.mediaCount(), 1); + QCOMPARE(playlist.media(0).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); + // check ability to load from QNetworkRequest loadSpy.clear(); From 9c020cd39ad9e81239a0953fb3039b79cbee0433 Mon Sep 17 00:00:00 2001 From: Thiago Macieira Date: Wed, 3 Sep 2014 17:41:02 -0700 Subject: [PATCH 24/48] Add necessary includes that were indirect qstringlist.h no longer includes qdatastream.h Change-Id: Iabc231741ab0d8c8edbb4f77a4c6fe45c6a98896 Reviewed-by: Yoann Lopes --- src/plugins/coreaudio/coreaudiodeviceinfo.mm | 1 + src/plugins/coreaudio/coreaudioinput.h | 1 + src/plugins/coreaudio/coreaudioinput.mm | 1 + src/plugins/coreaudio/coreaudiooutput.h | 1 + src/plugins/coreaudio/coreaudiooutput.mm | 1 + 5 files changed, 5 insertions(+) diff --git a/src/plugins/coreaudio/coreaudiodeviceinfo.mm b/src/plugins/coreaudio/coreaudiodeviceinfo.mm index ac41a310..2faeac8c 100644 --- a/src/plugins/coreaudio/coreaudiodeviceinfo.mm +++ b/src/plugins/coreaudio/coreaudiodeviceinfo.mm @@ -45,6 +45,7 @@ # include "coreaudiosessionmanager.h" #endif +#include #include #include diff --git a/src/plugins/coreaudio/coreaudioinput.h b/src/plugins/coreaudio/coreaudioinput.h index 64691eb3..533b7fef 100644 --- a/src/plugins/coreaudio/coreaudioinput.h +++ b/src/plugins/coreaudio/coreaudioinput.h @@ -38,6 +38,7 @@ #include #include +#include #include #include #include diff --git a/src/plugins/coreaudio/coreaudioinput.mm b/src/plugins/coreaudio/coreaudioinput.mm index a0b9e9d2..972f959f 100644 --- a/src/plugins/coreaudio/coreaudioinput.mm +++ b/src/plugins/coreaudio/coreaudioinput.mm @@ -52,6 +52,7 @@ #endif #include +#include #include QT_BEGIN_NAMESPACE diff --git a/src/plugins/coreaudio/coreaudiooutput.h b/src/plugins/coreaudio/coreaudiooutput.h index 84083ace..23b22937 100644 --- a/src/plugins/coreaudio/coreaudiooutput.h +++ b/src/plugins/coreaudio/coreaudiooutput.h @@ -41,6 +41,7 @@ #include #include +#include #include #include diff --git a/src/plugins/coreaudio/coreaudiooutput.mm b/src/plugins/coreaudio/coreaudiooutput.mm index 812d9dfe..7bb5c0e1 100644 --- a/src/plugins/coreaudio/coreaudiooutput.mm +++ b/src/plugins/coreaudio/coreaudiooutput.mm @@ -43,6 +43,7 @@ #include "coreaudiodeviceinfo.h" #include "coreaudioutils.h" +#include #include #include From 5be252432804ebf50992c266dad9c93bd039ed41 Mon Sep 17 00:00:00 2001 From: Nodir Temirkhodjaev Date: Fri, 5 Sep 2014 16:29:41 +0500 Subject: [PATCH 25/48] WMF: fix memory leaks. Release requested interfaces. Task-number: QTBUG-32481 Change-Id: I846981f6a7a7ea77588b9322fc41e05e583bdb15 Reviewed-by: Wouter Huysentruit Reviewed-by: Alex Blasche Reviewed-by: Jeff Tranter Reviewed-by: Allan Sandfeld Jensen --- .../wmf/decoder/mfaudiodecodercontrol.cpp | 3 +- src/plugins/wmf/player/mfplayersession.cpp | 56 ++++++++++--------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/plugins/wmf/decoder/mfaudiodecodercontrol.cpp b/src/plugins/wmf/decoder/mfaudiodecodercontrol.cpp index 22eb1a09..d9122e5d 100644 --- a/src/plugins/wmf/decoder/mfaudiodecodercontrol.cpp +++ b/src/plugins/wmf/decoder/mfaudiodecodercontrol.cpp @@ -244,7 +244,6 @@ void MFAudioDecoderControl::handleMediaSourceReady() } if (m_sourceResolver->mediaSource()) { - IMFPresentationDescriptor *pd = 0; if (mediaType && m_resampler) { HRESULT hr = S_OK; hr = m_resampler->SetInputType(m_mfInputStreamID, mediaType, 0); @@ -254,9 +253,11 @@ void MFAudioDecoderControl::handleMediaSourceReady() 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); diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp index 09c5a8c5..4b83e722 100644 --- a/src/plugins/wmf/player/mfplayersession.cpp +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -266,6 +266,7 @@ void MFPlayerSession::handleMediaSourceReady() //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); @@ -423,12 +424,15 @@ IMFTopologyNode* MFPlayerSession::addOutputNode(IMFStreamDescriptor *streamDesc, if (SUCCEEDED(hr)) { hr = node->SetUINT32(MF_TOPONODE_STREAMID, sinkID); if (SUCCEEDED(hr)) { - if (SUCCEEDED(topology->AddNode(node))) + if (SUCCEEDED(topology->AddNode(node))) { + handler->Release(); return node; + } } } } } + handler->Release(); } node->Release(); return NULL; @@ -617,42 +621,39 @@ HRESULT BindOutputNode(IMFTopologyNode *pNode) // Sets the IMFStreamSink pointers on all of the output nodes in a topology. HRESULT BindOutputNodes(IMFTopology *pTopology) { - DWORD cNodes = 0; - - IMFCollection *collection = NULL; - IUnknown *element = NULL; - IMFTopologyNode *node = NULL; + IMFCollection *collection; // Get the collection of output nodes. HRESULT hr = pTopology->GetOutputNodeCollection(&collection); // Enumerate all of the nodes in the collection. - if (SUCCEEDED(hr)) + if (SUCCEEDED(hr)) { + DWORD cNodes; hr = collection->GetElementCount(&cNodes); - if (SUCCEEDED(hr)) { - for (DWORD i = 0; i < cNodes; i++) { - hr = collection->GetElement(i, &element); - if (FAILED(hr)) - break; + if (SUCCEEDED(hr)) { + for (DWORD i = 0; i < cNodes; i++) { + IUnknown *element; + hr = collection->GetElement(i, &element); + if (FAILED(hr)) + break; - hr = element->QueryInterface(IID_IMFTopologyNode, (void**)&node); - 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); - if (FAILED(hr)) - break; + // Bind this node. + hr = BindOutputNode(node); + node->Release(); + if (FAILED(hr)) + break; + } } + collection->Release(); } - if (collection) - collection->Release(); - if (element) - element->Release(); - if (node) - node->Release(); return hr; } @@ -1510,8 +1511,11 @@ HRESULT MFPlayerSession::Invoke(IMFAsyncResult *pResult) } } - if (!m_closing) + if (!m_closing) { emit sessionEvent(pEvent); + } else { + pEvent->Release(); + } return S_OK; } From 55ce8ed07233efbfc224a0bfd6d45ad81b24ac99 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Thu, 17 Jul 2014 06:34:30 +0000 Subject: [PATCH 26/48] Implement encoder settings in camerabin backend. This is not comprehensive since different encoders have different names and representations for many settings, but it should cover bit rate for most encoders, and quality and encodingMode for a number of common encoders. Change-Id: I0ba4e70c2f234e0deaaa02bdecc0f5198122c1e9 Reviewed-by: Yoann Lopes --- .../camerabin/camerabinaudioencoder.cpp | 24 +++++++++++ .../camerabin/camerabinaudioencoder.h | 2 + .../gstreamer/camerabin/camerabinsession.cpp | 37 ++++++++++++++++ .../gstreamer/camerabin/camerabinsession.h | 4 ++ .../camerabin/camerabinvideoencoder.cpp | 42 +++++++++++++++++++ .../camerabin/camerabinvideoencoder.h | 2 + 6 files changed, 111 insertions(+) diff --git a/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp index fe8098a0..865a764a 100644 --- a/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp @@ -117,4 +117,28 @@ GstEncodingProfile *CameraBinAudioEncoder::createProfile() return profile; } +void CameraBinAudioEncoder::applySettings(GstElement *encoder) +{ + GObjectClass * const objectClass = G_OBJECT_GET_CLASS(encoder); + const char * const name = gst_plugin_feature_get_name( + GST_PLUGIN_FEATURE(gst_element_get_factory(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/plugins/gstreamer/camerabin/camerabinaudioencoder.h b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h index 46f812a6..f7143655 100644 --- a/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h +++ b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h @@ -78,6 +78,8 @@ public: GstEncodingProfile *createProfile(); + void applySettings(GstElement *element); + Q_SIGNALS: void settingsChanged(); diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.cpp b/src/plugins/gstreamer/camerabin/camerabinsession.cpp index 420ce585..850f8c16 100644 --- a/src/plugins/gstreamer/camerabin/camerabinsession.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinsession.cpp @@ -133,6 +133,7 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa m_capsFilter(0), m_fileSink(0), m_audioEncoder(0), + m_videoEncoder(0), m_muxer(0) { if (m_sourceFactory) @@ -140,6 +141,8 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa m_camerabin = gst_element_factory_make("camerabin2", "camerabin2"); 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); @@ -344,6 +347,9 @@ void CameraBinSession::setupCaptureResolution() } else { g_object_set(m_camerabin, VIEWFINDER_CAPS_PROPERTY, NULL, NULL); } + + if (m_videoEncoder) + m_videoEncodeControl->applySettings(m_videoEncoder); } void CameraBinSession::setAudioCaptureCaps() @@ -370,6 +376,9 @@ void CameraBinSession::setAudioCaptureCaps() 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() @@ -1293,4 +1302,32 @@ QList CameraBinSession::supportedResolutions(QPair rate, 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); + } +} + +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; +} + QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.h b/src/plugins/gstreamer/camerabin/camerabinsession.h index 42ed0e98..7e8a46de 100644 --- a/src/plugins/gstreamer/camerabin/camerabinsession.h +++ b/src/plugins/gstreamer/camerabin/camerabinsession.h @@ -190,6 +190,9 @@ private: void setAudioCaptureCaps(); static void updateBusyStatus(GObject *o, GParamSpec *p, gpointer d); + 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; @@ -241,6 +244,7 @@ private: GstElement *m_capsFilter; GstElement *m_fileSink; GstElement *m_audioEncoder; + GstElement *m_videoEncoder; GstElement *m_muxer; public: diff --git a/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp index 22805431..55443704 100644 --- a/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp @@ -175,4 +175,46 @@ GstEncodingProfile *CameraBinVideoEncoder::createProfile() return (GstEncodingProfile *)profile; } +void CameraBinVideoEncoder::applySettings(GstElement *encoder) +{ + GObjectClass * const objectClass = G_OBJECT_GET_CLASS(encoder); + const char * const name = gst_plugin_feature_get_name( + GST_PLUGIN_FEATURE(gst_element_get_factory(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/plugins/gstreamer/camerabin/camerabinvideoencoder.h b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h index 0838ba65..dabe098a 100644 --- a/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h +++ b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h @@ -76,6 +76,8 @@ public: GstEncodingProfile *createProfile(); + void applySettings(GstElement *encoder); + Q_SIGNALS: void settingsChanged(); From 569dd64083f3d54515db97f6333d59cbab21f528 Mon Sep 17 00:00:00 2001 From: Nodir Temirkhodjaev Date: Sat, 30 Aug 2014 16:41:30 +0500 Subject: [PATCH 27/48] WMF: fix initializing of media player's volume. According to the docs, MESessionTopologyStatus with status == MF_TOPOSTATUS_READY should be the correct place for the GetService call. Change-Id: I7fdbedbe43b2191b35b95c7fd9c86940f58daff7 Reviewed-by: Wouter Huysentruit Reviewed-by: Yoann Lopes --- src/plugins/wmf/player/mfplayersession.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plugins/wmf/player/mfplayersession.cpp b/src/plugins/wmf/player/mfplayersession.cpp index 4b83e722..0de0bdfd 100644 --- a/src/plugins/wmf/player/mfplayersession.cpp +++ b/src/plugins/wmf/player/mfplayersession.cpp @@ -1638,9 +1638,6 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) } } - if (SUCCEEDED(MFGetService(m_session, MR_STREAM_VOLUME_SERVICE, IID_PPV_ARGS(&m_volumeControl)))) - setVolumeInternal(m_muted ? 0 : m_volume); - DWORD dwCharacteristics = 0; m_sourceResolver->mediaSource()->GetCharacteristics(&dwCharacteristics); emit seekableUpdate(MFMEDIASOURCE_CAN_SEEK & dwCharacteristics); @@ -1711,6 +1708,9 @@ void MFPlayerSession::handleSessionEvent(IMFMediaEvent *sessionEvent) } } 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); } } } From e195b7fc05d62cce626693a8d791e58466dd3ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Wed, 10 Sep 2014 10:49:08 +0200 Subject: [PATCH 28/48] Remove unused includes Change-Id: Ibbce6e9135649d5dce0522320197dbbd0a92b3b9 Reviewed-by: Yoann Lopes --- src/multimedia/audio/qsoundeffect_qaudio_p.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/multimedia/audio/qsoundeffect_qaudio_p.cpp b/src/multimedia/audio/qsoundeffect_qaudio_p.cpp index 2b4359cc..56166ea3 100644 --- a/src/multimedia/audio/qsoundeffect_qaudio_p.cpp +++ b/src/multimedia/audio/qsoundeffect_qaudio_p.cpp @@ -53,9 +53,6 @@ #include "qsoundeffect_qaudio_p.h" #include -#include -#include -#include #include //#include From 8d5114a63051128992291356440ecff9ab2ef6b6 Mon Sep 17 00:00:00 2001 From: Jens Cornelis Date: Fri, 5 Sep 2014 11:13:16 +0200 Subject: [PATCH 29/48] Fixed avfmediaplayersession.mm for OSX 10.10 Yosemite. Implicit cast caused build to fail. Explicit cast builds fine. Task-number: QTBUG-41136 Change-Id: I0147c26a0f8b8198d6ed9944311547b81a50bccb Reviewed-by: Yoann Lopes --- src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm index 106e81a3..d6f0607a 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm @@ -295,7 +295,7 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe //AVPlayerItem "status" property value observer. if (context == AVFMediaPlayerSessionObserverStatusObservationContext) { - AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue]; + AVPlayerStatus status = (AVPlayerStatus)[[change objectForKey:NSKeyValueChangeNewKey] integerValue]; switch (status) { //Indicates that the status of the player is not yet known because From 65d44edd3d06674ec7a0014d7ecabc4f1d0854e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Str=C3=B8mme?= Date: Wed, 10 Sep 2014 11:52:28 +0200 Subject: [PATCH 30/48] OpenSL ES: Be less rigid about tearing down the output device. We where very strict about tearing down the audio device. While this is a good strategy to avoid unnecessary resource usage, it also causes excessive re-allocations, e.g., when transiting from start to stop and back again. This can increase latency, especially in case where a short clip is re-played at a high frequency. This change also decrease the chance of the player ending up in some unknown state where it drops audio clips without any warning. Task-number: QTBUG-40864 Change-Id: I1afad4af0622983f0f0c221d91cf794585d8cad2 Reviewed-by: Yoann Lopes --- src/plugins/opensles/qopenslesaudiooutput.cpp | 65 +++++++++++-------- src/plugins/opensles/qopenslesaudiooutput.h | 3 + 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/plugins/opensles/qopenslesaudiooutput.cpp b/src/plugins/opensles/qopenslesaudiooutput.cpp index f6583e54..06f2261d 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.cpp +++ b/src/plugins/opensles/qopenslesaudiooutput.cpp @@ -71,7 +71,8 @@ QOpenSLESAudioOutput::QOpenSLESAudioOutput(const QByteArray &device) m_elapsedTime(0), m_processedBytes(0), m_availableBuffers(BUFFER_COUNT), - m_eventMask(SL_PLAYEVENT_HEADATEND) + m_eventMask(SL_PLAYEVENT_HEADATEND), + m_startRequiresInit(true) { #ifndef ANDROID m_streamType = -1; @@ -99,13 +100,10 @@ QAudio::State QOpenSLESAudioOutput::state() const void QOpenSLESAudioOutput::start(QIODevice *device) { Q_ASSERT(device); - destroyPlayer(); - - m_pullMode = true; - if (!preparePlayer()) return; + m_pullMode = true; m_audioSource = device; setState(QAudio::ActiveState); setError(QAudio::NoError); @@ -126,29 +124,20 @@ void QOpenSLESAudioOutput::start(QIODevice *device) // Change the state to playing. // We need to do this after filling the buffers or processedBytes might get corrupted. - if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { - setError(QAudio::FatalError); - destroyPlayer(); - } + startPlayer(); } QIODevice *QOpenSLESAudioOutput::start() { - destroyPlayer(); - - m_pullMode = false; - if (!preparePlayer()) return Q_NULLPTR; + m_pullMode = false; m_audioSource = new SLIODevicePrivate(this); m_audioSource->open(QIODevice::WriteOnly | QIODevice::Unbuffered); // Change the state to playing - if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { - setError(QAudio::FatalError); - destroyPlayer(); - } + startPlayer(); setState(QAudio::IdleState); return m_audioSource; @@ -159,7 +148,7 @@ void QOpenSLESAudioOutput::stop() if (m_state == QAudio::StoppedState) return; - destroyPlayer(); + stopPlayer(); setError(QAudio::NoError); } @@ -181,6 +170,7 @@ void QOpenSLESAudioOutput::setBufferSize(int value) if (m_state != QAudio::StoppedState) return; + m_startRequiresInit = true; m_bufferSize = value; } @@ -254,6 +244,7 @@ void QOpenSLESAudioOutput::resume() void QOpenSLESAudioOutput::setFormat(const QAudioFormat &format) { + m_startRequiresInit = true; m_format = format; } @@ -325,6 +316,7 @@ void QOpenSLESAudioOutput::setCategory(const QString &category) return; } + m_startRequiresInit = true; m_streamType = streamType; m_category = category; #endif // ANDROID @@ -403,6 +395,11 @@ void QOpenSLESAudioOutput::bufferQueueCallback(SLBufferQueueItf bufferQueue, voi bool QOpenSLESAudioOutput::preparePlayer() { + if (m_startRequiresInit) + destroyPlayer(); + else + return true; + SLEngineItf engine = QOpenSLESEngine::instance()->slEngine(); if (!engine) { qWarning() << "No engine"; @@ -543,20 +540,15 @@ bool QOpenSLESAudioOutput::preparePlayer() m_clockStamp.restart(); setError(QAudio::NoError); + m_startRequiresInit = false; return true; } void QOpenSLESAudioOutput::destroyPlayer() { - setState(QAudio::StoppedState); - - // We need to change the state manually... - if (m_playItf) - (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED); - - if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf)) - qWarning() << "Unable to clear buffer"; + if (m_state != QAudio::StoppedState) + stopPlayer(); if (m_playerObject) { (*m_playerObject)->Destroy(m_playerObject); @@ -582,6 +574,27 @@ void QOpenSLESAudioOutput::destroyPlayer() m_playItf = Q_NULLPTR; m_volumeItf = Q_NULLPTR; m_bufferQueueItf = Q_NULLPTR; + m_startRequiresInit = true; +} + +void QOpenSLESAudioOutput::stopPlayer() +{ + setState(QAudio::StoppedState); + + // We need to change the state manually... + if (m_playItf) + (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_STOPPED); + + if (m_bufferQueueItf && SL_RESULT_SUCCESS != (*m_bufferQueueItf)->Clear(m_bufferQueueItf)) + qWarning() << "Unable to clear buffer"; +} + +void QOpenSLESAudioOutput::startPlayer() +{ + if (SL_RESULT_SUCCESS != (*m_playItf)->SetPlayState(m_playItf, SL_PLAYSTATE_PLAYING)) { + setError(QAudio::FatalError); + destroyPlayer(); + } } qint64 QOpenSLESAudioOutput::writeData(const char *data, qint64 len) diff --git a/src/plugins/opensles/qopenslesaudiooutput.h b/src/plugins/opensles/qopenslesaudiooutput.h index d466ea64..200b4a3c 100644 --- a/src/plugins/opensles/qopenslesaudiooutput.h +++ b/src/plugins/opensles/qopenslesaudiooutput.h @@ -86,6 +86,8 @@ private: bool preparePlayer(); void destroyPlayer(); + void stopPlayer(); + void startPlayer(); qint64 writeData(const char *data, qint64 len); void setState(QAudio::State state); @@ -113,6 +115,7 @@ private: qint64 m_processedBytes; QAtomicInt m_availableBuffers; SLuint32 m_eventMask; + bool m_startRequiresInit; qint32 m_streamType; QTime m_clockStamp; From 3b20608fe3a68375934aee93031ca78f87364bf4 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Mon, 11 Aug 2014 16:51:36 +1000 Subject: [PATCH 31/48] Ensure pre-roll frames are displayed when gstreamer backend is paused. Perform a seek before transitioning from the stopped state to paused or playing to force the pipeline to resupply the video sink with any pre-roll buffer it may have previously ignored during loading. And don't assume showPrerollFrames to be true if the current state is not stopped as the policy handling may have prevented an effectual state change. Change-Id: I288a70bc4da32f3534eab4b14702ca8f8fdb4222 Reviewed-by: Yoann Lopes --- src/gsttools/qvideosurfacegstsink.cpp | 86 +--------------- .../gsttools_headers/qvideosurfacegstsink_p.h | 13 +-- .../mediaplayer/qgstreamerplayercontrol.cpp | 99 +++++++++---------- .../mediaplayer/qgstreamerplayercontrol.h | 3 - 4 files changed, 51 insertions(+), 150 deletions(-) diff --git a/src/gsttools/qvideosurfacegstsink.cpp b/src/gsttools/qvideosurfacegstsink.cpp index 36f8c049..f3e2d884 100644 --- a/src/gsttools/qvideosurfacegstsink.cpp +++ b/src/gsttools/qvideosurfacegstsink.cpp @@ -56,7 +56,6 @@ QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate( : m_surface(surface) , m_pool(0) , m_renderReturn(GST_FLOW_ERROR) - , m_lastPrerolledBuffer(0) , m_bytesPerLine(0) , m_startCanceled(false) { @@ -74,7 +73,6 @@ QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate( QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate() { - setLastPrerolledBuffer(0); } QList QVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const @@ -209,23 +207,6 @@ GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer) return m_renderReturn; } -void QVideoSurfaceGstDelegate::setLastPrerolledBuffer(GstBuffer *prerolledBuffer) -{ - // discard previously stored buffer - if (m_lastPrerolledBuffer) { - gst_buffer_unref(m_lastPrerolledBuffer); - m_lastPrerolledBuffer = 0; - } - - if (!prerolledBuffer) - return; - - // store a reference to the buffer - Q_ASSERT(!m_lastPrerolledBuffer); - m_lastPrerolledBuffer = prerolledBuffer; - gst_buffer_ref(m_lastPrerolledBuffer); -} - void QVideoSurfaceGstDelegate::queuedStart() { if (!m_startCanceled) { @@ -397,8 +378,6 @@ QVideoSurfaceGstSink *QVideoSurfaceGstSink::createSink(QAbstractVideoSurface *su sink->delegate = new QVideoSurfaceGstDelegate(surface); - g_signal_connect(G_OBJECT(sink), "notify::show-preroll-frame", G_CALLBACK(handleShowPrerollChange), sink); - return sink; } @@ -434,16 +413,15 @@ void QVideoSurfaceGstSink::class_init(gpointer g_class, gpointer class_data) sink_parent_class = reinterpret_cast(g_type_class_peek_parent(g_class)); + GstVideoSinkClass *video_sink_class = reinterpret_cast(g_class); + video_sink_class->show_frame = QVideoSurfaceGstSink::show_frame; + GstBaseSinkClass *base_sink_class = reinterpret_cast(g_class); base_sink_class->get_caps = QVideoSurfaceGstSink::get_caps; base_sink_class->set_caps = QVideoSurfaceGstSink::set_caps; base_sink_class->buffer_alloc = QVideoSurfaceGstSink::buffer_alloc; base_sink_class->start = QVideoSurfaceGstSink::start; base_sink_class->stop = QVideoSurfaceGstSink::stop; - // base_sink_class->unlock = QVideoSurfaceGstSink::unlock; // Not implemented. - base_sink_class->event = QVideoSurfaceGstSink::event; - base_sink_class->preroll = QVideoSurfaceGstSink::preroll; - base_sink_class->render = QVideoSurfaceGstSink::render; GstElementClass *element_class = reinterpret_cast(g_class); element_class->change_state = QVideoSurfaceGstSink::change_state; @@ -709,27 +687,6 @@ void QVideoSurfaceGstSink::setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buf } } -void QVideoSurfaceGstSink::handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d) -{ - Q_UNUSED(o); - Q_UNUSED(p); - QVideoSurfaceGstSink *sink = reinterpret_cast(d); - - gboolean value = true; // "show-preroll-frame" property is true by default - g_object_get(G_OBJECT(sink), "show-preroll-frame", &value, NULL); - - GstBuffer *buffer = sink->delegate->lastPrerolledBuffer(); - // Render the stored prerolled buffer if requested. - // e.g. player is in stopped mode, then seek operation is requested, - // surface now stores a prerolled frame, but doesn't display it until - // "show-preroll-frame" property is set to "true" - // when switching to pause or playing state. - if (value && buffer) { - sink->delegate->render(buffer); - sink->delegate->setLastPrerolledBuffer(0); - } -} - GstFlowReturn QVideoSurfaceGstSink::buffer_alloc( GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer) { @@ -842,44 +799,9 @@ gboolean QVideoSurfaceGstSink::stop(GstBaseSink *base) return TRUE; } -gboolean QVideoSurfaceGstSink::unlock(GstBaseSink *base) -{ - Q_UNUSED(base); - - return TRUE; -} - -gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event) -{ - // discard prerolled frame - if (event->type == GST_EVENT_FLUSH_START) { - VO_SINK(base); - sink->delegate->setLastPrerolledBuffer(0); - } - - return TRUE; -} - -GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer) +GstFlowReturn QVideoSurfaceGstSink::show_frame(GstVideoSink *base, GstBuffer *buffer) { VO_SINK(base); - - gboolean value = true; // "show-preroll-frame" property is true by default - g_object_get(G_OBJECT(base), "show-preroll-frame", &value, NULL); - if (value) { - sink->delegate->setLastPrerolledBuffer(0); // discard prerolled buffer - return sink->delegate->render(buffer); // display frame - } - - // otherwise keep a reference to the buffer to display it later - sink->delegate->setLastPrerolledBuffer(buffer); - return GST_FLOW_OK; -} - -GstFlowReturn QVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer) -{ - VO_SINK(base); - sink->delegate->setLastPrerolledBuffer(0); // discard prerolled buffer return sink->delegate->render(buffer); } diff --git a/src/multimedia/gsttools_headers/qvideosurfacegstsink_p.h b/src/multimedia/gsttools_headers/qvideosurfacegstsink_p.h index d152e2a4..11b305d2 100644 --- a/src/multimedia/gsttools_headers/qvideosurfacegstsink_p.h +++ b/src/multimedia/gsttools_headers/qvideosurfacegstsink_p.h @@ -84,9 +84,6 @@ public: GstFlowReturn render(GstBuffer *buffer); - GstBuffer *lastPrerolledBuffer() const { return m_lastPrerolledBuffer; } - void setLastPrerolledBuffer(GstBuffer *lastPrerolledBuffer); // set prerolledBuffer to 0 to discard prerolled buffer - private slots: void queuedStart(); void queuedStop(); @@ -108,8 +105,6 @@ private: QVideoSurfaceFormat m_format; QVideoFrame m_frame; GstFlowReturn m_renderReturn; - // this pointer is not 0 when there is a prerolled buffer waiting to be displayed - GstBuffer *m_lastPrerolledBuffer; int m_bytesPerLine; bool m_started; bool m_startCanceled; @@ -126,8 +121,6 @@ public: QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle); static void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer); - static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d); - private: static GType get_type(); static void class_init(gpointer g_class, gpointer class_data); @@ -147,11 +140,7 @@ private: static gboolean start(GstBaseSink *sink); static gboolean stop(GstBaseSink *sink); - static gboolean unlock(GstBaseSink *sink); - - static gboolean event(GstBaseSink *sink, GstEvent *event); - static GstFlowReturn preroll(GstBaseSink *sink, GstBuffer *buffer); - static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer); + static GstFlowReturn show_frame(GstVideoSink *sink, GstBuffer *buffer); private: QVideoSurfaceGstDelegate *delegate; diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp index 2f7047f9..fed756ac 100644 --- a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp @@ -60,7 +60,6 @@ QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *sessio , m_currentState(QMediaPlayer::StoppedState) , m_mediaStatus(QMediaPlayer::NoMedia) , m_bufferProgress(-1) - , m_seekToStartPending(false) , m_pendingSeekPosition(-1) , m_setMediaPending(false) , m_stream(0) @@ -69,7 +68,7 @@ QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *sessio Q_ASSERT(m_resources); connect(m_session, SIGNAL(positionChanged(qint64)), - this, SLOT(updatePosition(qint64))); + this, SIGNAL(positionChanged(qint64))); connect(m_session, SIGNAL(durationChanged(qint64)), this, SIGNAL(durationChanged(qint64))); connect(m_session, SIGNAL(mutedStateChanged(bool)), @@ -94,8 +93,6 @@ QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *sessio this, SLOT(handleInvalidMedia())); connect(m_session, SIGNAL(playbackRateChanged(qreal)), this, SIGNAL(playbackRateChanged(qreal))); - connect(m_session, SIGNAL(seekableChanged(bool)), - this, SLOT(applyPendingSeek(bool))); connect(m_resources, SIGNAL(resourcesGranted()), SLOT(handleResourcesGranted())); //denied signal should be queued to have correct state update process, @@ -117,7 +114,7 @@ QMediaPlayerResourceSetInterface* QGstreamerPlayerControl::resources() const qint64 QGstreamerPlayerControl::position() const { - return m_seekToStartPending ? 0 : m_session->position(); + return m_pendingSeekPosition != -1 ? m_pendingSeekPosition : m_session->position(); } qint64 QGstreamerPlayerControl::duration() const @@ -183,15 +180,21 @@ void QGstreamerPlayerControl::setPosition(qint64 pos) if (m_mediaStatus == QMediaPlayer::EndOfMedia) { m_mediaStatus = QMediaPlayer::LoadedMedia; - m_seekToStartPending = true; } - if (m_session->isSeekable() && m_session->seek(pos)) { - m_seekToStartPending = false; - } else { + if (m_currentState == QMediaPlayer::StoppedState) { m_pendingSeekPosition = pos; - //don't display the first video frame since it's not what user requested. - m_session->showPrerollFrames(false); + emit positionChanged(m_pendingSeekPosition); + } else if (m_session->isSeekable()) { + m_session->showPrerollFrames(true); + m_session->seek(pos); + m_pendingSeekPosition = -1; + } else if (m_session->state() == QMediaPlayer::StoppedState) { + m_pendingSeekPosition = pos; + emit positionChanged(m_pendingSeekPosition); + } else if (m_pendingSeekPosition != -1) { + m_pendingSeekPosition = -1; + emit positionChanged(m_pendingSeekPosition); } popAndNotifyState(); @@ -239,26 +242,30 @@ void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState) } #endif + if (m_mediaStatus == QMediaPlayer::EndOfMedia && m_pendingSeekPosition == -1) { + m_pendingSeekPosition = 0; + } + if (!m_resources->isGranted()) m_resources->acquire(); if (m_resources->isGranted()) { - if (m_seekToStartPending) { + // show prerolled frame if switching from stopped state + if (m_pendingSeekPosition == -1) { + m_session->showPrerollFrames(true); + } else if (m_session->state() == QMediaPlayer::StoppedState) { + // Don't evaluate the next two conditions. + } else if (m_session->isSeekable()) { m_session->pause(); - if (!m_session->seek(0)) { - m_bufferProgress = -1; - m_session->stop(); - m_mediaStatus = QMediaPlayer::LoadingMedia; - } - m_seekToStartPending = false; + m_session->showPrerollFrames(true); + m_session->seek(m_pendingSeekPosition); + m_pendingSeekPosition = -1; + } else { + m_pendingSeekPosition = -1; } bool ok = false; - // show prerolled frame if switching from stopped state - if (newState != QMediaPlayer::StoppedState && m_currentState == QMediaPlayer::StoppedState && m_pendingSeekPosition == -1) - m_session->showPrerollFrames(true); - //To prevent displaying the first video frame when playback is resumed //the pipeline is paused instead of playing, seeked to requested position, //and after seeking is finished (position updated) playback is restarted @@ -305,7 +312,7 @@ void QGstreamerPlayerControl::stop() m_session->pause(); if (m_mediaStatus != QMediaPlayer::EndOfMedia) { - m_seekToStartPending = true; + m_pendingSeekPosition = 0; emit positionChanged(position()); } } @@ -343,7 +350,7 @@ void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice * m_currentState = QMediaPlayer::StoppedState; QMediaContent oldMedia = m_currentResource; - m_pendingSeekPosition = -1; + m_pendingSeekPosition = 0; m_session->showPrerollFrames(false); // do not show prerolled frames until pause() or play() explicitly called m_setMediaPending = false; @@ -390,7 +397,6 @@ void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice * m_currentResource = content; m_stream = stream; - m_seekToStartPending = false; QNetworkRequest request; @@ -462,8 +468,21 @@ void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state) { pushState(); - if (state == QMediaPlayer::StoppedState) + if (state == QMediaPlayer::StoppedState) { + m_session->showPrerollFrames(false); m_currentState = QMediaPlayer::StoppedState; + } + + if (state == QMediaPlayer::PausedState && m_currentState != QMediaPlayer::StoppedState) { + if (m_pendingSeekPosition != -1 && m_session->isSeekable()) { + m_session->showPrerollFrames(true); + m_session->seek(m_pendingSeekPosition); + } + m_pendingSeekPosition = -1; + + if (m_currentState == QMediaPlayer::PlayingState) + m_session->play(); + } updateMediaStatus(); @@ -512,7 +531,6 @@ void QGstreamerPlayerControl::processEOS() m_mediaStatus = QMediaPlayer::EndOfMedia; emit positionChanged(position()); m_session->endOfMediaReset(); - m_setMediaPending = true; if (m_currentState != QMediaPlayer::StoppedState) { m_currentState = QMediaPlayer::StoppedState; @@ -549,17 +567,12 @@ void QGstreamerPlayerControl::setBufferProgress(int progress) emit bufferStatusChanged(m_bufferProgress); } -void QGstreamerPlayerControl::applyPendingSeek(bool isSeekable) -{ - if (isSeekable && m_pendingSeekPosition != -1) - setPosition(m_pendingSeekPosition); -} - void QGstreamerPlayerControl::handleInvalidMedia() { pushState(); m_mediaStatus = QMediaPlayer::InvalidMedia; m_currentState = QMediaPlayer::StoppedState; + m_setMediaPending = true; popAndNotifyState(); } @@ -636,24 +649,4 @@ void QGstreamerPlayerControl::popAndNotifyState() } } -void QGstreamerPlayerControl::updatePosition(qint64 pos) -{ -#ifdef DEBUG_PLAYBIN - qDebug() << Q_FUNC_INFO << pos/1000.0 << "pending:" << m_pendingSeekPosition/1000.0; -#endif - - if (m_pendingSeekPosition != -1) { - //seek request is complete, it's safe to resume playback - //with prerolled frame displayed - m_pendingSeekPosition = -1; - if (m_currentState != QMediaPlayer::StoppedState) - m_session->showPrerollFrames(true); - if (m_currentState == QMediaPlayer::PlayingState) { - m_session->play(); - } - } - - emit positionChanged(pos); -} - QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h index 94f7b701..0a5f8af8 100644 --- a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h @@ -103,8 +103,6 @@ private Q_SLOTS: void updateMediaStatus(); void processEOS(); void setBufferProgress(int progress); - void applyPendingSeek(bool isSeekable); - void updatePosition(qint64 pos); void handleInvalidMedia(); @@ -127,7 +125,6 @@ private: QStack m_mediaStatusStack; int m_bufferProgress; - bool m_seekToStartPending; qint64 m_pendingSeekPosition; bool m_setMediaPending; QMediaContent m_currentResource; From 46c56ed530b91671067c8416bab2c4037ad57f9d Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 16 Sep 2014 16:28:12 +0200 Subject: [PATCH 32/48] Revert "QMediaNetworkPlaylistProvider: Upon error parsing, stop parsing." This reverts commit 0ed18d846c0b425b0c50a2fefd7cc0fc148832c2. The same patch was submitted in both 5.3 and 5.4 branches. It should have been pushed only to 5.3. Change-Id: I9e2974886190dfb2e5def5bc325c58f6e7783f55 Reviewed-by: Robin Burchell --- .../qmedianetworkplaylistprovider.cpp | 2 - .../unit/qmediaplaylist/testdata/test.pls | 27 ------ .../testdata/totem-pl-example.pls | 5 - .../unit/qmediaplaylist/testdata/trash.pls | 2 - .../qmediaplaylist/tst_qmediaplaylist.cpp | 92 +------------------ 5 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 tests/auto/unit/qmediaplaylist/testdata/test.pls delete mode 100644 tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls delete mode 100644 tests/auto/unit/qmediaplaylist/testdata/trash.pls diff --git a/src/multimedia/playback/qmedianetworkplaylistprovider.cpp b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp index 2b89ef17..baf6474e 100644 --- a/src/multimedia/playback/qmedianetworkplaylistprovider.cpp +++ b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp @@ -82,8 +82,6 @@ void QMediaNetworkPlaylistProviderPrivate::_q_handleParserError(QPlaylistFilePar break; } - parser.stop(); - emit q->loadFailed(playlistError, errorMessage); } diff --git a/tests/auto/unit/qmediaplaylist/testdata/test.pls b/tests/auto/unit/qmediaplaylist/testdata/test.pls deleted file mode 100644 index 42a14f39..00000000 --- a/tests/auto/unit/qmediaplaylist/testdata/test.pls +++ /dev/null @@ -1,27 +0,0 @@ -[playlist] - -File1=http://test.host/path -Title1=First -Length1=-1 -File2= http://test.host/path -Title2=Second -Length2=-1 -File3=testfile -Title3=Third -Length3=-1 - - - -File4=testdir/testfile -Title4=Fourth -Length4=-1 -File5=/testdir/testfile -Title5=Fifth -Length5=-1 -File6=file://path/name#suffix -Title6=Sixth -Length6=-1 -File7=testfile2#suffix -Title7=Seventh -Length7=-1 -NumberOfEntries=7 diff --git a/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls b/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls deleted file mode 100644 index 385fe2a3..00000000 --- a/tests/auto/unit/qmediaplaylist/testdata/totem-pl-example.pls +++ /dev/null @@ -1,5 +0,0 @@ -[playlist] -X-GNOME-Title=totem-pl-file-example -NumberOfEntries=1 -File1=http://test.host/path -Title1=Silence diff --git a/tests/auto/unit/qmediaplaylist/testdata/trash.pls b/tests/auto/unit/qmediaplaylist/testdata/trash.pls deleted file mode 100644 index 639c22b0..00000000 --- a/tests/auto/unit/qmediaplaylist/testdata/trash.pls +++ /dev/null @@ -1,2 +0,0 @@ -[playlist] -NumberOfEntries=100 diff --git a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp index a1250e59..08d63c85 100644 --- a/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp +++ b/tests/auto/unit/qmediaplaylist/tst_qmediaplaylist.cpp @@ -78,7 +78,6 @@ private slots: void currentItem(); void saveAndLoad(); void loadM3uFile(); - void loadPLSFile(); void playbackMode(); void playbackMode_data(); void shuffle(); @@ -349,10 +348,8 @@ void tst_QMediaPlaylist::saveAndLoad() QVERIFY(playlist.error() == QMediaPlaylist::FormatNotSupportedError); QVERIFY(!playlist.errorString().isEmpty()); - QSignalSpy loadedSignal(&playlist, SIGNAL(loaded())); QSignalSpy errorSignal(&playlist, SIGNAL(loadFailed())); playlist.load(&buffer, "unsupported_format"); - QTRY_VERIFY(loadedSignal.isEmpty()); QCOMPARE(errorSignal.size(), 1); QVERIFY(playlist.error() != QMediaPlaylist::NoError); QVERIFY(!playlist.errorString().isEmpty()); @@ -362,10 +359,8 @@ void tst_QMediaPlaylist::saveAndLoad() QVERIFY(playlist.error() != QMediaPlaylist::NoError); QVERIFY(!playlist.errorString().isEmpty()); - loadedSignal.clear(); errorSignal.clear(); playlist.load(QUrl::fromLocalFile(QLatin1String("tmp.unsupported_format")), "unsupported_format"); - QTRY_VERIFY(loadedSignal.isEmpty()); QCOMPARE(errorSignal.size(), 1); QVERIFY(playlist.error() == QMediaPlaylist::FormatNotSupportedError); QVERIFY(!playlist.errorString().isEmpty()); @@ -377,11 +372,7 @@ void tst_QMediaPlaylist::saveAndLoad() buffer.seek(0); QMediaPlaylist playlist2; - QSignalSpy loadedSignal2(&playlist2, SIGNAL(loaded())); - QSignalSpy errorSignal2(&playlist2, SIGNAL(loadFailed())); playlist2.load(&buffer, "m3u"); - QCOMPARE(loadedSignal2.size(), 1); - QTRY_VERIFY(errorSignal2.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), playlist2.mediaCount()); @@ -391,13 +382,9 @@ void tst_QMediaPlaylist::saveAndLoad() res = playlist.save(QUrl::fromLocalFile(QLatin1String("tmp.m3u")), "m3u"); QVERIFY(res); - loadedSignal2.clear(); - errorSignal2.clear(); playlist2.clear(); QVERIFY(playlist2.isEmpty()); playlist2.load(QUrl::fromLocalFile(QLatin1String("tmp.m3u")), "m3u"); - QCOMPARE(loadedSignal2.size(), 1); - QTRY_VERIFY(errorSignal2.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), playlist2.mediaCount()); @@ -411,20 +398,12 @@ void tst_QMediaPlaylist::loadM3uFile() QMediaPlaylist playlist; // Try to load playlist that does not exist in the testdata folder - QSignalSpy loadSpy(&playlist, SIGNAL(loaded())); - QSignalSpy loadFailedSpy(&playlist, SIGNAL(loadFailed())); QString testFileName = QFINDTESTDATA("testdata"); playlist.load(QUrl::fromLocalFile(testFileName + "/missing_file.m3u")); - QTRY_VERIFY(loadSpy.isEmpty()); - QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(playlist.error() != QMediaPlaylist::NoError); - loadSpy.clear(); - loadFailedSpy.clear(); testFileName = QFINDTESTDATA("testdata/test.m3u"); playlist.load(QUrl::fromLocalFile(testFileName)); - QTRY_VERIFY(!loadSpy.isEmpty()); - QVERIFY(loadFailedSpy.isEmpty()); QCOMPARE(playlist.error(), QMediaPlaylist::NoError); QCOMPARE(playlist.mediaCount(), 7); @@ -441,79 +420,10 @@ void tst_QMediaPlaylist::loadM3uFile() //ensure #2 suffix is not stripped from path testFileName = QFINDTESTDATA("testdata/testfile2#suffix"); QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); - // check ability to load from QNetworkRequest - loadSpy.clear(); - loadFailedSpy.clear(); - playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.m3u")))); - QTRY_VERIFY(!loadSpy.isEmpty()); - QVERIFY(loadFailedSpy.isEmpty()); -} - -void tst_QMediaPlaylist::loadPLSFile() -{ - QMediaPlaylist playlist; - - // Try to load playlist that does not exist in the testdata folder QSignalSpy loadSpy(&playlist, SIGNAL(loaded())); QSignalSpy loadFailedSpy(&playlist, SIGNAL(loadFailed())); - QString testFileName = QFINDTESTDATA("testdata"); - playlist.load(QUrl::fromLocalFile(testFileName + "/missing_file.pls")); - QTRY_VERIFY(loadSpy.isEmpty()); - QVERIFY(!loadFailedSpy.isEmpty()); - QVERIFY(playlist.error() != QMediaPlaylist::NoError); - - // Try to load bogus playlist - loadSpy.clear(); - loadFailedSpy.clear(); - testFileName = QFINDTESTDATA("testdata/trash.pls"); - playlist.load(QUrl::fromLocalFile(testFileName)); - QTRY_VERIFY(loadSpy.isEmpty()); - QVERIFY(!loadFailedSpy.isEmpty()); - QVERIFY(playlist.error() == QMediaPlaylist::FormatError); - - // Try to load regular playlist - loadSpy.clear(); - loadFailedSpy.clear(); - testFileName = QFINDTESTDATA("testdata/test.pls"); - playlist.load(QUrl::fromLocalFile(testFileName)); - QTRY_VERIFY(!loadSpy.isEmpty()); - QVERIFY(loadFailedSpy.isEmpty()); - QCOMPARE(playlist.error(), QMediaPlaylist::NoError); - QCOMPARE(playlist.mediaCount(), 7); - - QCOMPARE(playlist.media(0).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); - QCOMPARE(playlist.media(1).canonicalUrl(), QUrl(QLatin1String("http://test.host/path"))); - testFileName = QFINDTESTDATA("testdata/testfile"); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QCOMPARE(playlist.media(2).canonicalUrl(), - QUrl::fromLocalFile(testFileName)); - testFileName = QFINDTESTDATA("testdata"); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QCOMPARE(playlist.media(3).canonicalUrl(), - QUrl::fromLocalFile(testFileName + "/testdir/testfile")); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QCOMPARE(playlist.media(4).canonicalUrl(), QUrl(QLatin1String("file:///testdir/testfile"))); - QCOMPARE(playlist.media(5).canonicalUrl(), QUrl(QLatin1String("file://path/name#suffix"))); - //ensure #2 suffix is not stripped from path - testFileName = QFINDTESTDATA("testdata/testfile2#suffix"); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); - - // Try to load a totem-pl generated playlist - loadSpy.clear(); - loadFailedSpy.clear(); - testFileName = QFINDTESTDATA("testdata/totem-pl-example.pls"); - playlist.load(QUrl::fromLocalFile(testFileName)); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QTRY_VERIFY(!loadSpy.isEmpty()); - QEXPECT_FAIL("", "See QTBUG-40515", Continue); - QVERIFY(loadFailedSpy.isEmpty()); - - // check ability to load from QNetworkRequest - loadSpy.clear(); - loadFailedSpy.clear(); - playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.pls")))); + playlist.load(QNetworkRequest(QUrl::fromLocalFile(QFINDTESTDATA("testdata/test.m3u")))); QTRY_VERIFY(!loadSpy.isEmpty()); QVERIFY(loadFailedSpy.isEmpty()); } From da383e5c7e3cbf94c054a9418a8b53ab4619bb83 Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Thu, 18 Sep 2014 11:06:04 +0200 Subject: [PATCH 33/48] Remove QtSystemInfo dependency from Multimedia examples QtSystemInfo is not officially supported in Qt5 and its QML API is currently broken. Inhibition of the screensaver is not essential for the example anyway. Task-number: QTBUG-31080 Change-Id: Ia2e7d8de3e2044e11e1681907e61a7207489ad1b Reviewed-by: Yoann Lopes --- .../qml/qmlvideo/DisableScreenSaver.qml | 42 ------------------ .../video/qmlvideo/qml/qmlvideo/main.qml | 6 --- .../multimedia/video/qmlvideo/qmlvideo.qrc | 1 - .../qml/qmlvideofx/DisableScreenSaver.qml | 43 ------------------- .../video/qmlvideofx/qmlvideofx.qrc | 1 - 5 files changed, 93 deletions(-) delete mode 100644 examples/multimedia/video/qmlvideo/qml/qmlvideo/DisableScreenSaver.qml delete mode 100644 examples/multimedia/video/qmlvideofx/qml/qmlvideofx/DisableScreenSaver.qml diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/DisableScreenSaver.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/DisableScreenSaver.qml deleted file mode 100644 index 74f951b1..00000000 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/DisableScreenSaver.qml +++ /dev/null @@ -1,42 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.0 -import QtSystemInfo 5.0 -// NOTE: The QtSystemInfo module is not yet part of Qt 5 - -Item { - ScreenSaver { - screenSaverInhibited: true - } -} diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml index 4422648c..a65d3e55 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml @@ -53,12 +53,6 @@ Rectangle { property int margins: 10 } - // Create ScreenSaver element via Loader, so this app will still run if the - // SystemInfo module is not available - Loader { - source: "DisableScreenSaver.qml" - } - Loader { id: performanceLoader diff --git a/examples/multimedia/video/qmlvideo/qmlvideo.qrc b/examples/multimedia/video/qmlvideo/qmlvideo.qrc index 5bf2df8a..9471eb6b 100644 --- a/examples/multimedia/video/qmlvideo/qmlvideo.qrc +++ b/examples/multimedia/video/qmlvideo/qmlvideo.qrc @@ -21,7 +21,6 @@ qml/qmlvideo/CameraRotate.qml qml/qmlvideo/CameraSpin.qml qml/qmlvideo/Content.qml - qml/qmlvideo/DisableScreenSaver.qml qml/qmlvideo/ErrorDialog.qml qml/qmlvideo/FileBrowser.qml qml/qmlvideo/main.qml diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/DisableScreenSaver.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/DisableScreenSaver.qml deleted file mode 100644 index ccd852bf..00000000 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/DisableScreenSaver.qml +++ /dev/null @@ -1,43 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.0 -import QtMobility.systeminfo 1.1 -// NOTE: The QtSystemInfo module is not yet part of Qt 5 - -Item { - ScreenSaver { - screenSaverInhibited: true - } -} - diff --git a/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc b/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc index e7a36124..f3ad2770 100644 --- a/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc +++ b/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc @@ -6,7 +6,6 @@ qml/qmlvideofx/ContentCamera.qml qml/qmlvideofx/ContentImage.qml qml/qmlvideofx/ContentVideo.qml - qml/qmlvideofx/DisableScreenSaver.qml qml/qmlvideofx/Divider.qml qml/qmlvideofx/Effect.qml qml/qmlvideofx/EffectBillboard.qml From f788f8e5c65a3d07a64d1507cc21714292dc6efd Mon Sep 17 00:00:00 2001 From: Alex Blasche Date: Thu, 18 Sep 2014 11:33:46 +0200 Subject: [PATCH 34/48] Make the multimedia examples less verbose Important warnings such as missing services are still printed though. Task-number: QTBUG-31080 Change-Id: I96fd3837c4edc58c61b97bf950b7cab05ceed014 Reviewed-by: Friedemann Kleint Reviewed-by: Christian Stromme --- examples/multimedia/audioinput/audioinput.cpp | 19 +------------------ examples/multimedia/audioinput/audioinput.h | 2 -- .../multimedia/audiooutput/audiooutput.cpp | 18 +----------------- examples/multimedia/audiooutput/audiooutput.h | 2 -- .../video/qmlvideo/qml/qmlvideo/Content.qml | 8 -------- .../qmlvideo/qml/qmlvideo/SceneBasic.qml | 1 - .../video/qmlvideo/qml/qmlvideo/main.qml | 3 --- .../video/qmlvideofx/filereader.cpp | 2 -- .../qmlvideofx/qml/qmlvideofx/Content.qml | 7 ------- .../video/qmlvideofx/qml/qmlvideofx/Main.qml | 6 ------ .../frequencymonitor/frequencymonitor.cpp | 2 -- .../multimedia/qdeclarativeradiodata.cpp | 3 +++ 12 files changed, 5 insertions(+), 68 deletions(-) diff --git a/examples/multimedia/audioinput/audioinput.cpp b/examples/multimedia/audioinput/audioinput.cpp index 09f571b0..87d7963a 100644 --- a/examples/multimedia/audioinput/audioinput.cpp +++ b/examples/multimedia/audioinput/audioinput.cpp @@ -315,20 +315,11 @@ void InputTest::initializeAudio() void InputTest::createAudioInput() { m_audioInput = new QAudioInput(m_device, m_format, this); - connect(m_audioInput, SIGNAL(notify()), SLOT(notified())); - connect(m_audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(handleStateChanged(QAudio::State))); m_volumeSlider->setValue(m_audioInput->volume() * 100); m_audioInfo->start(); m_audioInput->start(m_audioInfo); } -void InputTest::notified() -{ - qWarning() << "bytesReady = " << m_audioInput->bytesReady() - << ", " << "elapsedUSecs = " <elapsedUSecs() - << ", " << "processedUSecs = "<processedUSecs(); -} - void InputTest::readMore() { if (!m_audioInput) @@ -364,27 +355,19 @@ void InputTest::toggleSuspend() { // toggle suspend/resume if (m_audioInput->state() == QAudio::SuspendedState) { - qWarning() << "status: Suspended, resume()"; m_audioInput->resume(); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); } else if (m_audioInput->state() == QAudio::ActiveState) { - qWarning() << "status: Active, suspend()"; m_audioInput->suspend(); m_suspendResumeButton->setText(tr(RESUME_LABEL)); } else if (m_audioInput->state() == QAudio::StoppedState) { - qWarning() << "status: Stopped, resume()"; m_audioInput->resume(); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); } else if (m_audioInput->state() == QAudio::IdleState) { - qWarning() << "status: IdleState"; + // no-op } } -void InputTest::handleStateChanged(QAudio::State state) -{ - qWarning() << "state = " << state; -} - void InputTest::refreshDisplay() { m_canvas->setLevel(m_audioInfo->level()); diff --git a/examples/multimedia/audioinput/audioinput.h b/examples/multimedia/audioinput/audioinput.h index 82def563..df26104b 100644 --- a/examples/multimedia/audioinput/audioinput.h +++ b/examples/multimedia/audioinput/audioinput.h @@ -110,11 +110,9 @@ private: private slots: void refreshDisplay(); - void notified(); void readMore(); void toggleMode(); void toggleSuspend(); - void handleStateChanged(QAudio::State state); void deviceChanged(int index); void sliderChanged(int value); diff --git a/examples/multimedia/audiooutput/audiooutput.cpp b/examples/multimedia/audiooutput/audiooutput.cpp index daa7bfdf..7911b580 100644 --- a/examples/multimedia/audiooutput/audiooutput.cpp +++ b/examples/multimedia/audiooutput/audiooutput.cpp @@ -247,8 +247,6 @@ void AudioTest::createAudioOutput() delete m_audioOutput; m_audioOutput = 0; m_audioOutput = new QAudioOutput(m_device, m_format, this); - connect(m_audioOutput, SIGNAL(notify()), SLOT(notified())); - connect(m_audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(handleStateChanged(QAudio::State))); m_generator->start(); m_audioOutput->start(m_generator); m_volumeSlider->setValue(int(m_audioOutput->volume()*100.0f)); @@ -275,13 +273,6 @@ void AudioTest::volumeChanged(int value) m_audioOutput->setVolume(qreal(value/100.0f)); } -void AudioTest::notified() -{ - qWarning() << "bytesFree = " << m_audioOutput->bytesFree() - << ", " << "elapsedUSecs = " << m_audioOutput->elapsedUSecs() - << ", " << "processedUSecs = " << m_audioOutput->processedUSecs(); -} - void AudioTest::pullTimerExpired() { if (m_audioOutput && m_audioOutput->state() != QAudio::StoppedState) { @@ -319,23 +310,16 @@ void AudioTest::toggleMode() void AudioTest::toggleSuspendResume() { if (m_audioOutput->state() == QAudio::SuspendedState) { - qWarning() << "status: Suspended, resume()"; m_audioOutput->resume(); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); } else if (m_audioOutput->state() == QAudio::ActiveState) { - qWarning() << "status: Active, suspend()"; m_audioOutput->suspend(); m_suspendResumeButton->setText(tr(RESUME_LABEL)); } else if (m_audioOutput->state() == QAudio::StoppedState) { - qWarning() << "status: Stopped, resume()"; m_audioOutput->resume(); m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); } else if (m_audioOutput->state() == QAudio::IdleState) { - qWarning() << "status: IdleState"; + // no-op } } -void AudioTest::handleStateChanged(QAudio::State state) -{ - qWarning() << "state = " << state; -} diff --git a/examples/multimedia/audiooutput/audiooutput.h b/examples/multimedia/audiooutput/audiooutput.h index 30cf3536..80d6d327 100644 --- a/examples/multimedia/audiooutput/audiooutput.h +++ b/examples/multimedia/audiooutput/audiooutput.h @@ -110,11 +110,9 @@ private: QByteArray m_buffer; private slots: - void notified(); void pullTimerExpired(); void toggleMode(); void toggleSuspendResume(); - void handleStateChanged(QAudio::State state); void deviceChanged(int index); void volumeChanged(int); }; diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Content.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Content.qml index 30b1e32b..3094c1a3 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Content.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Content.qml @@ -97,21 +97,16 @@ Rectangle { } function initialize() { - console.log("[qmlvideo] Content.initialize: contentType " + contentType) if ("video" == contentType) { - console.log("[qmlvideo] Content.initialize: loading VideoItem.qml") contentLoader.source = "VideoItem.qml" if (Loader.Error == contentLoader.status) { - console.log("[qmlvideo] Content.initialize: loading VideoDummy.qml") contentLoader.source = "VideoDummy.qml" dummy = true } contentLoader.item.volume = volume } else if ("camera" == contentType) { - console.log("[qmlvideo] Content.initialize: loading CameraItem.qml") contentLoader.source = "CameraItem.qml" if (Loader.Error == contentLoader.status) { - console.log("[qmlvideo] Content.initialize: loading CameraDummy.qml") contentLoader.source = "CameraDummy.qml" dummy = true } @@ -127,12 +122,10 @@ Rectangle { if (root.autoStart) root.start() } - console.log("[qmlvideo] Content.initialize: complete") root.initialized() } function start() { - console.log("[qmlvideo] Content.start") if (contentLoader.item) { if (root.contentType == "video") contentLoader.item.mediaSource = root.source @@ -142,7 +135,6 @@ Rectangle { } function stop() { - console.log("[qmlvideo] Content.stop") if (contentLoader.item) { contentLoader.item.stop() if (root.contentType == "video") diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml index 560262db..cb50e365 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml @@ -67,7 +67,6 @@ Scene { MouseArea { anchors.fill: parent onClicked: { - console.log("[qmlvideo] SceneBasic.onClicked, started = " + content.started) if (content.started) content.stop() else diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml index a65d3e55..c2c15439 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml @@ -65,7 +65,6 @@ Rectangle { } function init() { - console.log("[qmlvideo] performanceLoader.init logging " + root.perfMonitorsLogging + " visible " + root.perfMonitorsVisible) var enabled = root.perfMonitorsLogging || root.perfMonitorsVisible source = enabled ? "../performancemonitor/PerformanceItem.qml" : "" } @@ -137,7 +136,6 @@ Rectangle { } radius: 10 onSceneSourceChanged: { - console.log("[qmlvideo] main.onSceneSourceChanged source " + sceneSource) sceneLoader.source = sceneSource var scene = null var innerVisible = true @@ -224,7 +222,6 @@ Rectangle { } function closeScene() { - console.log("[qmlvideo] main.closeScene") sceneSelectionPanel.sceneSource = "" } } diff --git a/examples/multimedia/video/qmlvideofx/filereader.cpp b/examples/multimedia/video/qmlvideofx/filereader.cpp index 5f5066f1..714c0914 100644 --- a/examples/multimedia/video/qmlvideofx/filereader.cpp +++ b/examples/multimedia/video/qmlvideofx/filereader.cpp @@ -32,7 +32,6 @@ ****************************************************************************/ #include "filereader.h" -#include "trace.h" #include #include @@ -42,7 +41,6 @@ QString FileReader::readFile(const QString &fileName) { - qtTrace() << "FileReader::readFile" << "fileName" << fileName; QString content; QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Content.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Content.qml index 0a2a1584..19785ce8 100644 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Content.qml +++ b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Content.qml @@ -84,7 +84,6 @@ Rectangle { } onEffectSourceChanged: { - console.log("[qmlvideofx] Content.onEffectSourceChanged " + effectSource) effectLoader.source = effectSource effectLoader.item.parent = root effectLoader.item.targetWidth = root.width @@ -96,7 +95,6 @@ Rectangle { } function init() { - console.log("[qmlvideofx] Content.init") openImage("qrc:/images/qt-logo.png") root.effectSource = "EffectPassThrough.qml" } @@ -107,7 +105,6 @@ Rectangle { } function updateSource() { - console.log("[qmlvideofx] Content.updateSource") if (contentLoader.item) { contentLoader.item.parent = root contentLoader.item.anchors.fill = root @@ -118,7 +115,6 @@ Rectangle { } function openImage(path) { - console.log("[qmlvideofx] Content.openImage \"" + path + "\"") stop() contentLoader.source = "ContentImage.qml" videoFramePaintedConnection.target = null @@ -127,7 +123,6 @@ Rectangle { } function openVideo(path) { - console.log("[qmlvideofx] Content.openVideo \"" + path + "\"") stop() contentLoader.source = "ContentVideo.qml" videoFramePaintedConnection.target = contentLoader.item @@ -138,7 +133,6 @@ Rectangle { } function openCamera() { - console.log("[qmlvideofx] Content.openCamera") stop() contentLoader.source = "ContentCamera.qml" videoFramePaintedConnection.target = contentLoader.item @@ -146,7 +140,6 @@ Rectangle { } function stop() { - console.log("[qmlvideofx] Content.stop") if (contentLoader.source == "ContentVideo.qml") contentLoader.item.stop() theSource.sourceItem = null diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Main.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Main.qml index 039e6c36..b724ee3b 100644 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Main.qml +++ b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/Main.qml @@ -160,7 +160,6 @@ Rectangle { Loader { id: performanceLoader function init() { - console.log("[qmlvideofx] performanceLoader.init logging " + root.perfMonitorsLogging + " visible " + root.perfMonitorsVisible) var enabled = root.perfMonitorsLogging || root.perfMonitorsVisible source = enabled ? "../performancemonitor/PerformanceItem.qml" : "" } @@ -249,11 +248,6 @@ Rectangle { height = windowHeight width = windowWidth - console.log("[qmlvideofx] root.init") - console.log("Height: ", Screen.desktopAvailableHeight) - console.log("Width: ", Screen.desktopAvailableWidth) - console.log("Pixels per mm: ", Math.ceil(Screen.pixelDensity)) - console.log("Orientation: ", Screen.orientation) imageFileBrowser.folder = imagePath videoFileBrowser.folder = videoPath content.init() diff --git a/examples/multimedia/video/snippets/frequencymonitor/frequencymonitor.cpp b/examples/multimedia/video/snippets/frequencymonitor/frequencymonitor.cpp index 747ea6bd..b165adad 100644 --- a/examples/multimedia/video/snippets/frequencymonitor/frequencymonitor.cpp +++ b/examples/multimedia/video/snippets/frequencymonitor/frequencymonitor.cpp @@ -125,7 +125,6 @@ void FrequencyMonitorPrivate::calculateAverageFrequency() void FrequencyMonitorPrivate::stalled() { if (m_instantaneousFrequency) { - qtVerboseTrace() << "FrequencyMonitor::stalled"; m_instantaneousFrequency = 0; emit q_ptr->instantaneousFrequencyChanged(m_instantaneousFrequency); emit q_ptr->frequencyChanged(); @@ -136,7 +135,6 @@ FrequencyMonitor::FrequencyMonitor(QObject *parent) : QObject(parent) { d_ptr = new FrequencyMonitorPrivate(this); - qtTrace() << "FrequencyMonitor::FrequencyMonitor"; } FrequencyMonitor::~FrequencyMonitor() diff --git a/src/imports/multimedia/qdeclarativeradiodata.cpp b/src/imports/multimedia/qdeclarativeradiodata.cpp index e8673905..6cc829ae 100644 --- a/src/imports/multimedia/qdeclarativeradiodata.cpp +++ b/src/imports/multimedia/qdeclarativeradiodata.cpp @@ -281,6 +281,9 @@ void QDeclarativeRadioData::_q_availabilityChanged(QMultimedia::AvailabilityStat void QDeclarativeRadioData::connectSignals() { + if (!m_radioData) + return; + connect(m_radioData, SIGNAL(programTypeChanged(QRadioData::ProgramType)), this, SLOT(_q_programTypeChanged(QRadioData::ProgramType))); From 78dccc998f51fad51caa6dc220fbfdf14ed5c113 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 23 Sep 2014 16:47:17 +0200 Subject: [PATCH 35/48] WMF: fix video rendering with ANGLE. The format of the offscreen surface used for video rendering should be compatible with the EGL config. Change-Id: Ic016245ce80c2483771e620c3eed345262d03c44 Reviewed-by: Laszlo Agocs --- src/plugins/wmf/evrd3dpresentengine.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/plugins/wmf/evrd3dpresentengine.cpp b/src/plugins/wmf/evrd3dpresentengine.cpp index bc27a7e0..2aa9d0d6 100644 --- a/src/plugins/wmf/evrd3dpresentengine.cpp +++ b/src/plugins/wmf/evrd3dpresentengine.cpp @@ -419,18 +419,19 @@ void D3DPresentEngine::createOffscreenTexture() QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); m_eglDisplay = static_cast( nativeInterface->nativeResourceForContext("eglDisplay", currentContext)); - m_eglConfig = static_cast( + m_eglConfig = static_cast( nativeInterface->nativeResourceForContext("eglConfig", currentContext)); currentContext->functions()->glGenTextures(1, &m_glTexture); int w = m_surfaceFormat.frameWidth(); int h = m_surfaceFormat.frameHeight(); + bool hasAlpha = currentContext->format().hasAlpha(); EGLint attribs[] = { EGL_WIDTH, w, EGL_HEIGHT, h, - EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGB, + EGL_TEXTURE_FORMAT, hasAlpha ? EGL_TEXTURE_RGBA : EGL_TEXTURE_RGB, EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, EGL_NONE }; @@ -449,7 +450,7 @@ void D3DPresentEngine::createOffscreenTexture() m_device->CreateTexture(w, h, 1, D3DUSAGE_RENDERTARGET, - D3DFMT_X8R8G8B8, + hasAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT, &m_texture, &share_handle); From 49dc6dc45956daa2b30ea3c56a5e5dfaac8ad9a1 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 23 Sep 2014 14:46:22 +0200 Subject: [PATCH 36/48] WMF: fix crash on media player destruction. A race condition could cause a frame to be presented even after the QAbstractVideoSurface was deleted. We now check that the surface is valid before presenting a frame. Task-number: QTBUG-41158 Change-Id: If593469a8267583e499e781336af38d3fbf318fd Reviewed-by: Christian Stromme --- src/plugins/wmf/evrd3dpresentengine.cpp | 52 +++++++++++++------------ 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/plugins/wmf/evrd3dpresentengine.cpp b/src/plugins/wmf/evrd3dpresentengine.cpp index 2aa9d0d6..42d0dea4 100644 --- a/src/plugins/wmf/evrd3dpresentengine.cpp +++ b/src/plugins/wmf/evrd3dpresentengine.cpp @@ -330,34 +330,36 @@ void D3DPresentEngine::presentSample(void *opaque, qint64) IMFMediaBuffer* buffer = NULL; IDirect3DSurface9* surface = NULL; - if (sample) { - // Get the buffer from the sample. - hr = sample->GetBufferByIndex(0, &buffer); - if (FAILED(hr)) - goto done; + if (m_surface && m_surface->isActive()) { + if (sample) { + // Get the buffer from the sample. + hr = sample->GetBufferByIndex(0, &buffer); + if (FAILED(hr)) + goto done; - // Get the surface from the buffer. - hr = MFGetService(buffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&surface)); - if (FAILED(hr)) - goto done; - } - - if (surface && updateTexture(surface)) { - QVideoFrame frame = QVideoFrame(new TextureVideoBuffer(m_glTexture), - m_surfaceFormat.frameSize(), - m_surfaceFormat.pixelFormat()); - - // WMF uses 100-nanosecond units, Qt uses microseconds - LONGLONG startTime = -1; - if (SUCCEEDED(sample->GetSampleTime(&startTime))) { - frame.setStartTime(startTime * 0.1); - - LONGLONG duration = -1; - if (SUCCEEDED(sample->GetSampleDuration(&duration))) - frame.setEndTime((startTime + duration) * 0.1); + // Get the surface from the buffer. + hr = MFGetService(buffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&surface)); + if (FAILED(hr)) + goto done; } - m_surface->present(frame); + if (surface && updateTexture(surface)) { + QVideoFrame frame = QVideoFrame(new TextureVideoBuffer(m_glTexture), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat()); + + // WMF uses 100-nanosecond units, Qt uses microseconds + LONGLONG startTime = -1; + if (SUCCEEDED(sample->GetSampleTime(&startTime))) { + frame.setStartTime(startTime * 0.1); + + LONGLONG duration = -1; + if (SUCCEEDED(sample->GetSampleDuration(&duration))) + frame.setEndTime((startTime + duration) * 0.1); + } + + m_surface->present(frame); + } } done: From c1c205b772c2ca0adb7b00fe02d27a59429dd068 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 16 Sep 2014 17:30:27 +0200 Subject: [PATCH 37/48] Android: fix QMediaPlayer's state and mediaStatus signals. Emit signals only after both properties are written to avoid having incoherent values in signal handlers. Task-number: QTBUG-40314 Change-Id: I6c8445e61cccf1a9803647329c4fa1f0e452f56d Reviewed-by: Christian Stromme --- .../qandroidmediaplayercontrol.cpp | 53 +++++++++++++++++-- .../mediaplayer/qandroidmediaplayercontrol.h | 3 ++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp index bd936f86..fb1c8b72 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp @@ -37,6 +37,36 @@ 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 (mPreviousState != mControl->state()) + Q_EMIT mControl->stateChanged(mControl->state()); + + if (mPreviousMediaStatus != mControl->mediaStatus()) + Q_EMIT mControl->mediaStatusChanged(mControl->mediaStatus()); + } + +private: + QAndroidMediaPlayerControl *mControl; + QMediaPlayer::State mPreviousState; + QMediaPlayer::MediaStatus mPreviousMediaStatus; +}; + + QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent) : QMediaPlayerControl(parent), mMediaPlayer(new AndroidMediaPlayer), @@ -55,7 +85,8 @@ QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent) mPendingPosition(-1), mPendingSetMedia(false), mPendingVolume(-1), - mPendingMute(-1) + mPendingMute(-1), + mActiveStateChangeNotifiers(0) { connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)), this,SLOT(onBufferingChanged(qint32))); @@ -138,6 +169,8 @@ void QAndroidMediaPlayerControl::setPosition(qint64 position) return; } + StateChangeNotifier notifier(this); + if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia) setMediaStatus(QMediaPlayer::LoadedMedia); @@ -275,6 +308,8 @@ const QIODevice *QAndroidMediaPlayerControl::mediaStream() const void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, QIODevice *stream) { + StateChangeNotifier notifier(this); + const bool reloading = (mMediaContent == mediaContent); if (!reloading) { @@ -346,6 +381,8 @@ void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput) void QAndroidMediaPlayerControl::play() { + StateChangeNotifier notifier(this); + // We need to prepare the mediaplayer again. if ((mState & AndroidMediaPlayer::Stopped) && !mMediaContent.isNull()) { setMedia(mMediaContent, mMediaStream); @@ -366,6 +403,8 @@ void QAndroidMediaPlayerControl::play() void QAndroidMediaPlayerControl::pause() { + StateChangeNotifier notifier(this); + setState(QMediaPlayer::PausedState); if ((mState & (AndroidMediaPlayer::Started @@ -380,6 +419,8 @@ void QAndroidMediaPlayerControl::pause() void QAndroidMediaPlayerControl::stop() { + StateChangeNotifier notifier(this); + setState(QMediaPlayer::StoppedState); if ((mState & (AndroidMediaPlayer::Prepared @@ -397,6 +438,8 @@ void QAndroidMediaPlayerControl::stop() void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra) { + StateChangeNotifier notifier(this); + Q_UNUSED(extra); switch (what) { case AndroidMediaPlayer::MEDIA_INFO_UNKNOWN: @@ -428,6 +471,8 @@ void QAndroidMediaPlayerControl::onInfo(qint32 what, qint32 extra) void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra) { + StateChangeNotifier notifier(this); + QString errorString; QMediaPlayer::Error error = QMediaPlayer::ResourceError; @@ -480,6 +525,8 @@ void QAndroidMediaPlayerControl::onError(qint32 what, qint32 extra) void QAndroidMediaPlayerControl::onBufferingChanged(qint32 percent) { + StateChangeNotifier notifier(this); + mBuffering = percent != 100; mBufferPercent = percent; @@ -511,6 +558,8 @@ void QAndroidMediaPlayerControl::onStateChanged(qint32 state) return; } + StateChangeNotifier notifier(this); + mState = state; switch (mState) { case AndroidMediaPlayer::Idle: @@ -599,7 +648,6 @@ void QAndroidMediaPlayerControl::setState(QMediaPlayer::State state) return; mCurrentState = state; - Q_EMIT stateChanged(mCurrentState); } void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status) @@ -614,7 +662,6 @@ void QAndroidMediaPlayerControl::setMediaStatus(QMediaPlayer::MediaStatus status Q_EMIT durationChanged(duration()); mCurrentMediaStatus = status; - Q_EMIT mediaStatusChanged(mCurrentMediaStatus); updateBufferStatus(); } diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h index ec4dd328..7de0c2dc 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h @@ -112,6 +112,7 @@ private: int mPendingVolume; int mPendingMute; QScopedPointer mTempFile; + int mActiveStateChangeNotifiers; void setState(QMediaPlayer::State state); void setMediaStatus(QMediaPlayer::MediaStatus status); @@ -122,6 +123,8 @@ private: void resetBufferingProgress(); void flushPendingStates(); void updateBufferStatus(); + + friend class StateChangeNotifier; }; QT_END_NAMESPACE From 2d9d5acb9c809dc5c5021bc8c00cae696ca52b1a Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 24 Sep 2014 18:26:11 +0200 Subject: [PATCH 38/48] Remove QuickTime backend. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backend was kept only for Mac OS X 10.6 support. AVFoundation is used in 10.7 and later. Following the 10.6 deprecation plan, the code is now being removed for Qt 5.4. Change-Id: I513d00adda35f6012dda548b8d824d158efffb16 Reviewed-by: Andy Nichols Reviewed-by: Tor Arne Vestbø --- src/plugins/plugins.pro | 2 - src/plugins/qt7/mediaplayer/mediaplayer.pri | 16 - .../qt7/mediaplayer/qt7playercontrol.h | 98 --- .../qt7/mediaplayer/qt7playercontrol.mm | 191 ----- .../qt7/mediaplayer/qt7playermetadata.h | 66 -- .../qt7/mediaplayer/qt7playermetadata.mm | 250 ------ .../qt7/mediaplayer/qt7playerservice.h | 73 -- .../qt7/mediaplayer/qt7playerservice.mm | 128 --- .../qt7/mediaplayer/qt7playersession.h | 183 ----- .../qt7/mediaplayer/qt7playersession.mm | 751 ------------------ src/plugins/qt7/qcvdisplaylink.h | 80 -- src/plugins/qt7/qcvdisplaylink.mm | 156 ---- src/plugins/qt7/qt7.json | 4 - src/plugins/qt7/qt7.pro | 67 -- src/plugins/qt7/qt7backend.h | 60 -- src/plugins/qt7/qt7backend.mm | 60 -- src/plugins/qt7/qt7ciimagevideobuffer.h | 78 -- src/plugins/qt7/qt7ciimagevideobuffer.mm | 107 --- src/plugins/qt7/qt7movierenderer.h | 98 --- src/plugins/qt7/qt7movierenderer.mm | 481 ----------- src/plugins/qt7/qt7movievideowidget.h | 117 --- src/plugins/qt7/qt7movievideowidget.mm | 437 ---------- src/plugins/qt7/qt7movieviewoutput.h | 107 --- src/plugins/qt7/qt7movieviewoutput.mm | 339 -------- src/plugins/qt7/qt7movieviewrenderer.h | 96 --- src/plugins/qt7/qt7movieviewrenderer.mm | 509 ------------ src/plugins/qt7/qt7serviceplugin.h | 69 -- src/plugins/qt7/qt7serviceplugin.mm | 118 --- src/plugins/qt7/qt7videooutput.h | 109 --- src/plugins/qt7/qt7videooutput.mm | 91 --- 30 files changed, 4941 deletions(-) delete mode 100644 src/plugins/qt7/mediaplayer/mediaplayer.pri delete mode 100644 src/plugins/qt7/mediaplayer/qt7playercontrol.h delete mode 100644 src/plugins/qt7/mediaplayer/qt7playercontrol.mm delete mode 100644 src/plugins/qt7/mediaplayer/qt7playermetadata.h delete mode 100644 src/plugins/qt7/mediaplayer/qt7playermetadata.mm delete mode 100644 src/plugins/qt7/mediaplayer/qt7playerservice.h delete mode 100644 src/plugins/qt7/mediaplayer/qt7playerservice.mm delete mode 100644 src/plugins/qt7/mediaplayer/qt7playersession.h delete mode 100644 src/plugins/qt7/mediaplayer/qt7playersession.mm delete mode 100644 src/plugins/qt7/qcvdisplaylink.h delete mode 100644 src/plugins/qt7/qcvdisplaylink.mm delete mode 100644 src/plugins/qt7/qt7.json delete mode 100644 src/plugins/qt7/qt7.pro delete mode 100644 src/plugins/qt7/qt7backend.h delete mode 100644 src/plugins/qt7/qt7backend.mm delete mode 100644 src/plugins/qt7/qt7ciimagevideobuffer.h delete mode 100644 src/plugins/qt7/qt7ciimagevideobuffer.mm delete mode 100644 src/plugins/qt7/qt7movierenderer.h delete mode 100644 src/plugins/qt7/qt7movierenderer.mm delete mode 100644 src/plugins/qt7/qt7movievideowidget.h delete mode 100644 src/plugins/qt7/qt7movievideowidget.mm delete mode 100644 src/plugins/qt7/qt7movieviewoutput.h delete mode 100644 src/plugins/qt7/qt7movieviewoutput.mm delete mode 100644 src/plugins/qt7/qt7movieviewrenderer.h delete mode 100644 src/plugins/qt7/qt7movieviewrenderer.mm delete mode 100644 src/plugins/qt7/qt7serviceplugin.h delete mode 100644 src/plugins/qt7/qt7serviceplugin.mm delete mode 100644 src/plugins/qt7/qt7videooutput.h delete mode 100644 src/plugins/qt7/qt7videooutput.mm diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index f03a138e..aa28bdd1 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -53,8 +53,6 @@ mac:!simulator { SUBDIRS += audiocapture coreaudio config_avfoundation: SUBDIRS += avfoundation - - contains(QT_CONFIG, opengl.*):!ios: SUBDIRS += qt7 } config_resourcepolicy { diff --git a/src/plugins/qt7/mediaplayer/mediaplayer.pri b/src/plugins/qt7/mediaplayer/mediaplayer.pri deleted file mode 100644 index 2edb1d2c..00000000 --- a/src/plugins/qt7/mediaplayer/mediaplayer.pri +++ /dev/null @@ -1,16 +0,0 @@ -INCLUDEPATH += $$PWD - -DEFINES += QMEDIA_QT7_PLAYER - -HEADERS += \ - $$PWD/qt7playercontrol.h \ - $$PWD/qt7playermetadata.h \ - $$PWD/qt7playerservice.h \ - $$PWD/qt7playersession.h - -OBJECTIVE_SOURCES += \ - $$PWD/qt7playercontrol.mm \ - $$PWD/qt7playermetadata.mm \ - $$PWD/qt7playerservice.mm \ - $$PWD/qt7playersession.mm - diff --git a/src/plugins/qt7/mediaplayer/qt7playercontrol.h b/src/plugins/qt7/mediaplayer/qt7playercontrol.h deleted file mode 100644 index 61fd0a45..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playercontrol.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7PLAYERCONTROL_H -#define QT7PLAYERCONTROL_H - -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -class QT7PlayerSession; -class QT7PlayerService; -class QMediaPlaylist; -class QMediaPlaylistNavigator; - -class QT7PlayerControl : public QMediaPlayerControl -{ -Q_OBJECT -public: - QT7PlayerControl(QObject *parent = 0); - ~QT7PlayerControl(); - - void setSession(QT7PlayerSession *session); - - QMediaPlayer::State state() const; - QMediaPlayer::MediaStatus mediaStatus() const; - - QMediaContent media() const; - const QIODevice *mediaStream() const; - void setMedia(const QMediaContent &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; - void setPlaybackRate(qreal rate); - -public Q_SLOTS: - void setPosition(qint64 pos); - - void play(); - void pause(); - void stop(); - - void setVolume(int volume); - void setMuted(bool muted); - -private: - QT7PlayerSession *m_session; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/mediaplayer/qt7playercontrol.mm b/src/plugins/qt7/mediaplayer/qt7playercontrol.mm deleted file mode 100644 index c4866077..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playercontrol.mm +++ /dev/null @@ -1,191 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7playercontrol.h" -#include "qt7playersession.h" - -#include - -#include -#include - -QT_USE_NAMESPACE - -QT7PlayerControl::QT7PlayerControl(QObject *parent) - : QMediaPlayerControl(parent) -{ -} - -QT7PlayerControl::~QT7PlayerControl() -{ -} - -void QT7PlayerControl::setSession(QT7PlayerSession *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(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))); -} - -qint64 QT7PlayerControl::position() const -{ - return m_session->position(); -} - -qint64 QT7PlayerControl::duration() const -{ - return m_session->duration(); -} - -QMediaPlayer::State QT7PlayerControl::state() const -{ - return m_session->state(); -} - -QMediaPlayer::MediaStatus QT7PlayerControl::mediaStatus() const -{ - return m_session->mediaStatus(); -} - -int QT7PlayerControl::bufferStatus() const -{ - return m_session->bufferStatus(); -} - -int QT7PlayerControl::volume() const -{ - return m_session->volume(); -} - -bool QT7PlayerControl::isMuted() const -{ - return m_session->isMuted(); -} - -bool QT7PlayerControl::isSeekable() const -{ - return m_session->isSeekable(); -} - -QMediaTimeRange QT7PlayerControl::availablePlaybackRanges() const -{ - return m_session->availablePlaybackRanges(); -} - -qreal QT7PlayerControl::playbackRate() const -{ - return m_session->playbackRate(); -} - -void QT7PlayerControl::setPlaybackRate(qreal rate) -{ - m_session->setPlaybackRate(rate); -} - -void QT7PlayerControl::setPosition(qint64 pos) -{ - m_session->setPosition(pos); -} - -void QT7PlayerControl::play() -{ - m_session->play(); -} - -void QT7PlayerControl::pause() -{ - m_session->pause(); -} - -void QT7PlayerControl::stop() -{ - m_session->stop(); -} - -void QT7PlayerControl::setVolume(int volume) -{ - m_session->setVolume(volume); -} - -void QT7PlayerControl::setMuted(bool muted) -{ - m_session->setMuted(muted); -} - -QMediaContent QT7PlayerControl::media() const -{ - return m_session->media(); -} - -const QIODevice *QT7PlayerControl::mediaStream() const -{ - return m_session->mediaStream(); -} - -void QT7PlayerControl::setMedia(const QMediaContent &content, QIODevice *stream) -{ - m_session->setMedia(content, stream); - - Q_EMIT mediaChanged(content); -} - -bool QT7PlayerControl::isAudioAvailable() const -{ - return m_session->isAudioAvailable(); -} - -bool QT7PlayerControl::isVideoAvailable() const -{ - return m_session->isVideoAvailable(); -} - - -#include "moc_qt7playercontrol.cpp" diff --git a/src/plugins/qt7/mediaplayer/qt7playermetadata.h b/src/plugins/qt7/mediaplayer/qt7playermetadata.h deleted file mode 100644 index 9f0ceae1..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playermetadata.h +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7PLAYERMETADATACONTROL_H -#define QT7PLAYERMETADATACONTROL_H - -#include - -QT_BEGIN_NAMESPACE - -class QT7PlayerSession; - -class QT7PlayerMetaDataControl : public QMetaDataReaderControl -{ - Q_OBJECT -public: - QT7PlayerMetaDataControl(QT7PlayerSession *session, QObject *parent); - virtual ~QT7PlayerMetaDataControl(); - - bool isMetaDataAvailable() const; - bool isWritable() const; - - QVariant metaData(const QString &key) const; - QStringList availableMetaData() const; - -private Q_SLOTS: - void updateTags(); - -private: - QT7PlayerSession *m_session; - QMap m_tags; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/mediaplayer/qt7playermetadata.mm b/src/plugins/qt7/mediaplayer/qt7playermetadata.mm deleted file mode 100644 index 84d434be..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playermetadata.mm +++ /dev/null @@ -1,250 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7backend.h" -#include "qt7playermetadata.h" -#include "qt7playersession.h" -#include -#include - -#import - -#ifdef QUICKTIME_C_API_AVAILABLE - #include - #undef check // avoid name clash; -#endif - -QT_USE_NAMESPACE - -QT7PlayerMetaDataControl::QT7PlayerMetaDataControl(QT7PlayerSession *session, QObject *parent) - :QMetaDataReaderControl(parent), m_session(session) -{ -} - -QT7PlayerMetaDataControl::~QT7PlayerMetaDataControl() -{ -} - -bool QT7PlayerMetaDataControl::isMetaDataAvailable() const -{ - return !m_tags.isEmpty(); -} - -bool QT7PlayerMetaDataControl::isWritable() const -{ - return false; -} - -QVariant QT7PlayerMetaDataControl::metaData(const QString &key) const -{ - return m_tags.value(key); -} - -QStringList QT7PlayerMetaDataControl::availableMetaData() const -{ - return m_tags.keys(); -} - -#ifdef QUICKTIME_C_API_AVAILABLE - -static QString stripCopyRightSymbol(const QString &key) -{ - return key.right(key.length()-1); -} - -static QString convertQuickTimeKeyToUserKey(const QString &key) -{ - if (key == QLatin1String("com.apple.quicktime.displayname")) - return QLatin1String("nam"); - else if (key == QLatin1String("com.apple.quicktime.album")) - return QLatin1String("alb"); - else if (key == QLatin1String("com.apple.quicktime.artist")) - return QLatin1String("ART"); - else - return QLatin1String("???"); -} - -static OSStatus readMetaValue(QTMetaDataRef metaDataRef, QTMetaDataItem item, QTPropertyClass propClass, - QTPropertyID id, QTPropertyValuePtr *value, ByteCount *size) -{ - QTPropertyValueType type; - ByteCount propSize; - UInt32 propFlags; - OSStatus err = QTMetaDataGetItemPropertyInfo(metaDataRef, item, propClass, id, &type, &propSize, &propFlags); - - if (err == noErr) { - *value = malloc(propSize); - if (*value != 0) { - err = QTMetaDataGetItemProperty(metaDataRef, item, propClass, id, propSize, *value, size); - - if (err == noErr && (type == 'code' || type == 'itsk' || type == 'itlk')) { - // convert from native endian to big endian - OSTypePtr pType = (OSTypePtr)*value; - *pType = EndianU32_NtoB(*pType); - } - } - else - return -1; - } - - return err; -} - -static UInt32 getMetaType(QTMetaDataRef metaDataRef, QTMetaDataItem item) -{ - QTPropertyValuePtr value = 0; - ByteCount ignore = 0; - OSStatus err = readMetaValue( - metaDataRef, item, kPropertyClass_MetaDataItem, kQTMetaDataItemPropertyID_DataType, &value, &ignore); - - if (err == noErr) { - UInt32 type = *((UInt32 *) value); - if (value) - free(value); - return type; - } - - return 0; -} - -static QString cFStringToQString(CFStringRef str) -{ - if(!str) - return QString(); - CFIndex length = CFStringGetLength(str); - const UniChar *chars = CFStringGetCharactersPtr(str); - if (chars) - return QString(reinterpret_cast(chars), length); - - QVarLengthArray buffer(length); - CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data()); - return QString(reinterpret_cast(buffer.constData()), length); -} - - -static QString getMetaValue(QTMetaDataRef metaDataRef, QTMetaDataItem item, SInt32 id) -{ - QTPropertyValuePtr value = 0; - ByteCount size = 0; - OSStatus err = readMetaValue(metaDataRef, item, kPropertyClass_MetaDataItem, id, &value, &size); - QString string; - - if (err == noErr) { - UInt32 dataType = getMetaType(metaDataRef, item); - switch (dataType){ - case kQTMetaDataTypeUTF8: - case kQTMetaDataTypeMacEncodedText: - string = cFStringToQString(CFStringCreateWithBytes(0, (UInt8*)value, size, kCFStringEncodingUTF8, false)); - break; - case kQTMetaDataTypeUTF16BE: - string = cFStringToQString(CFStringCreateWithBytes(0, (UInt8*)value, size, kCFStringEncodingUTF16BE, false)); - break; - default: - break; - } - - if (value) - free(value); - } - - return string; -} - - -static void readFormattedData(QTMetaDataRef metaDataRef, OSType format, QMultiMap &result) -{ - QTMetaDataItem item = kQTMetaDataItemUninitialized; - OSStatus err = QTMetaDataGetNextItem(metaDataRef, format, item, kQTMetaDataKeyFormatWildcard, 0, 0, &item); - while (err == noErr){ - QString key = getMetaValue(metaDataRef, item, kQTMetaDataItemPropertyID_Key); - if (format == kQTMetaDataStorageFormatQuickTime) - key = convertQuickTimeKeyToUserKey(key); - else - key = stripCopyRightSymbol(key); - - if (!result.contains(key)){ - QString val = getMetaValue(metaDataRef, item, kQTMetaDataItemPropertyID_Value); - result.insert(key, val); - } - err = QTMetaDataGetNextItem(metaDataRef, format, item, kQTMetaDataKeyFormatWildcard, 0, 0, &item); - } -} -#endif - - -void QT7PlayerMetaDataControl::updateTags() -{ - bool wasEmpty = m_tags.isEmpty(); - m_tags.clear(); - - QTMovie *movie = (QTMovie*)m_session->movie(); - - if (movie) { - QMultiMap metaMap; - -#ifdef QUICKTIME_C_API_AVAILABLE - QTMetaDataRef metaDataRef; - OSStatus err = QTCopyMovieMetaData([movie quickTimeMovie], &metaDataRef); - if (err == noErr) { - readFormattedData(metaDataRef, kQTMetaDataStorageFormatUserData, metaMap); - readFormattedData(metaDataRef, kQTMetaDataStorageFormatQuickTime, metaMap); - readFormattedData(metaDataRef, kQTMetaDataStorageFormatiTunes, metaMap); - } -#else - AutoReleasePool pool; - NSString *name = [movie attributeForKey:@"QTMovieDisplayNameAttribute"]; - metaMap.insert(QLatin1String("nam"), QString::fromUtf8([name UTF8String])); -#endif // QUICKTIME_C_API_AVAILABLE - - m_tags.insert(QMediaMetaData::AlbumArtist, metaMap.value(QLatin1String("ART"))); - m_tags.insert(QMediaMetaData::AlbumTitle, metaMap.value(QLatin1String("alb"))); - m_tags.insert(QMediaMetaData::Title, metaMap.value(QLatin1String("nam"))); - m_tags.insert(QMediaMetaData::Date, metaMap.value(QLatin1String("day"))); - m_tags.insert(QMediaMetaData::Genre, metaMap.value(QLatin1String("gnre"))); - m_tags.insert(QMediaMetaData::TrackNumber, metaMap.value(QLatin1String("trk"))); - m_tags.insert(QMediaMetaData::Description, metaMap.value(QLatin1String("des"))); - } - - if (!wasEmpty || !m_tags.isEmpty()) - Q_EMIT metaDataChanged(); -} - -#include "moc_qt7playermetadata.cpp" diff --git a/src/plugins/qt7/mediaplayer/qt7playerservice.h b/src/plugins/qt7/mediaplayer/qt7playerservice.h deleted file mode 100644 index 15c1d2cb..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playerservice.h +++ /dev/null @@ -1,73 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7PLAYERSERVICE_H -#define QT7PLAYERSERVICE_H - -#include -#include -#include - - -QT_BEGIN_NAMESPACE -class QMediaPlayerControl; -class QMediaPlaylist; -class QMediaPlaylistNavigator; -class QT7PlayerControl; -class QT7PlayerMetaDataControl; -class QT7VideoWindowControl; -class QT7VideoWidgetControl; -class QT7VideoRendererControl; -class QT7VideoOutput; -class QT7PlayerSession; - -class QT7PlayerService : public QMediaService -{ -Q_OBJECT -public: - QT7PlayerService(QObject *parent = 0); - ~QT7PlayerService(); - - QMediaControl* requestControl(const char *name); - void releaseControl(QMediaControl *control); - -private: - QT7PlayerSession *m_session; - QT7PlayerControl *m_control; - QMediaControl * m_videoOutput; - QT7PlayerMetaDataControl *m_playerMetaDataControl; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/mediaplayer/qt7playerservice.mm b/src/plugins/qt7/mediaplayer/qt7playerservice.mm deleted file mode 100644 index 46be5475..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playerservice.mm +++ /dev/null @@ -1,128 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include -#include - -#include "qt7backend.h" -#include "qt7playerservice.h" -#include "qt7playercontrol.h" -#include "qt7playersession.h" -#include "qt7videooutput.h" -#include "qt7movieviewoutput.h" -#include "qt7movieviewrenderer.h" -#include "qt7movierenderer.h" -#ifndef QT_NO_WIDGETS -#include "qt7movievideowidget.h" -#endif -#include "qt7playermetadata.h" - -#include -#include - -QT_USE_NAMESPACE - -QT7PlayerService::QT7PlayerService(QObject *parent): - QMediaService(parent), - m_videoOutput(0) -{ - m_session = new QT7PlayerSession(this); - - m_control = new QT7PlayerControl(this); - m_control->setSession(m_session); - - m_playerMetaDataControl = new QT7PlayerMetaDataControl(m_session, this); - connect(m_control, SIGNAL(mediaChanged(QMediaContent)), m_playerMetaDataControl, SLOT(updateTags())); -} - -QT7PlayerService::~QT7PlayerService() -{ -} - -QMediaControl *QT7PlayerService::requestControl(const char *name) -{ - if (qstrcmp(name, QMediaPlayerControl_iid) == 0) - return m_control; - - if (qstrcmp(name, QMetaDataReaderControl_iid) == 0) - return m_playerMetaDataControl; - -#ifndef QT_NO_OPENGL - if (!m_videoOutput) { - if (qstrcmp(name, QVideoWindowControl_iid) == 0) { - m_videoOutput = new QT7MovieViewOutput(this); - } - - if (qstrcmp(name, QVideoRendererControl_iid) == 0) { -#ifndef QT_NO_WIDGETS - m_videoOutput = new QT7MovieViewRenderer(this); -#endif - } - -#ifndef QT_NO_WIDGETS - if (qstrcmp(name, QVideoWidgetControl_iid) == 0) { -#ifdef QUICKTIME_C_API_AVAILABLE - m_videoOutput = new QT7MovieVideoWidget(this); -#endif - } -#endif - - if (m_videoOutput) { - QT7VideoOutput *videoOutput = qobject_cast(m_videoOutput); - m_session->setVideoOutput(videoOutput); - return m_videoOutput; - } - } -#endif // !defined(QT_NO_OPENGL) - - return 0; -} - -void QT7PlayerService::releaseControl(QMediaControl *control) -{ - if (m_videoOutput == control) { - m_videoOutput = 0; - m_session->setVideoOutput(0); - delete control; - } -} - -#include "moc_qt7playerservice.cpp" diff --git a/src/plugins/qt7/mediaplayer/qt7playersession.h b/src/plugins/qt7/mediaplayer/qt7playersession.h deleted file mode 100644 index 58ea76a0..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playersession.h +++ /dev/null @@ -1,183 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7PLAYERSESSION_H -#define QT7PLAYERSESSION_H - -#include -#include -#include -#include - -#include -#include - -QT_BEGIN_NAMESPACE - -class QT7PlayerControl; -class QMediaPlaylist; -class QMediaPlaylistNavigator; -class QT7VideoOutput; -class QT7PlayerSession; -class QT7PlayerService; - - -class QT7PlayerSession : public QObject -{ - Q_OBJECT -public: - QT7PlayerSession(QObject *parent = 0); - ~QT7PlayerSession(); - - void *movie() const; - - void setControl(QT7PlayerControl *control); - - void setVideoOutput(QT7VideoOutput *output); - - QMediaPlayer::State state() const; - QMediaPlayer::MediaStatus mediaStatus() const; - - QMediaContent media() const; - const QIODevice *mediaStream() const; - void setMedia(const QMediaContent &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(); - void processVolumeChange(); - void processNaturalSizeChange(); - void processPositionChange(); - -Q_SIGNALS: - void positionChanged(qint64 position); - void durationChanged(qint64 duration); - void stateChanged(QMediaPlayer::State newState); - void mediaStatusChanged(QMediaPlayer::MediaStatus status); - void volumeChanged(int volume); - void mutedChanged(bool muted); - void audioAvailableChanged(bool audioAvailable); - void videoAvailableChanged(bool videoAvailable); - void error(int error, const QString &errorString); - -private: - class ResourceHandler { - public: - ResourceHandler():resource(0) {} - ~ResourceHandler() { clear(); } - void setResourceFile(const QString &file) { - if (resource) { - if (resource->fileName() == file) - return; - delete resource; - rawData.clear(); - } - resource = new QResource(file); - } - bool isValid() const { return resource && resource->isValid() && resource->data() != 0; } - const uchar *data() { - if (!isValid()) - return 0; - if (resource->isCompressed()) { - if (rawData.size() == 0) - rawData = qUncompress(resource->data(), resource->size()); - return (const uchar *)rawData.constData(); - } - return resource->data(); - } - qint64 size() { - if (data() == 0) - return 0; - return resource->isCompressed() ? rawData.size() : resource->size(); - } - void clear() { - delete resource; - rawData.clear(); - } - QResource *resource; - QByteArray rawData; - }; - - void openMovie(bool tryAsync); - - void *m_QTMovie; - void *m_movieObserver; - - QMediaPlayer::State m_state; - QMediaPlayer::MediaStatus m_mediaStatus; - QIODevice *m_mediaStream; - QMediaContent m_resources; - ResourceHandler m_resourceHandler; - - QT7VideoOutput * m_videoOutput; - - bool m_muted; - bool m_tryingAsync; - int m_volume; - qreal m_rate; - - qint64 m_duration; - bool m_videoAvailable; - bool m_audioAvailable; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/mediaplayer/qt7playersession.mm b/src/plugins/qt7/mediaplayer/qt7playersession.mm deleted file mode 100644 index 4ec015cb..00000000 --- a/src/plugins/qt7/mediaplayer/qt7playersession.mm +++ /dev/null @@ -1,751 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#import -#import - -#include "qt7backend.h" - -#include "qt7playersession.h" -#include "qt7playercontrol.h" -#include "qt7videooutput.h" - -#include -#include - -#include -#include - -#include -#include - -#include - -QT_USE_NAMESPACE - -//#define QT_DEBUG_QT7 - -@interface QTMovieObserver : NSObject -{ -@private - QT7PlayerSession *m_session; - QTMovie *m_movie; -} - -- (QTMovieObserver *) initWithPlayerSession:(QT7PlayerSession*)session; -- (void) setMovie:(QTMovie *)movie; -- (void) processEOS:(NSNotification *)notification; -- (void) processLoadStateChange:(NSNotification *)notification; -- (void) processVolumeChange:(NSNotification *)notification; -- (void) processNaturalSizeChange :(NSNotification *)notification; -- (void) processPositionChange :(NSNotification *)notification; -@end - -@implementation QTMovieObserver - -- (QTMovieObserver *) initWithPlayerSession:(QT7PlayerSession*)session -{ - if (!(self = [super init])) - return nil; - - self->m_session = session; - return self; -} - -- (void) setMovie:(QTMovie *)movie -{ - if (m_movie == movie) - return; - - if (m_movie) { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - [m_movie release]; - } - - m_movie = movie; - - if (movie) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processEOS:) - name:QTMovieDidEndNotification - object:m_movie]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processLoadStateChange:) - name:QTMovieLoadStateDidChangeNotification - object:m_movie]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processVolumeChange:) - name:QTMovieVolumeDidChangeNotification - object:m_movie]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processPositionChange:) - name:QTMovieTimeDidChangeNotification - object:m_movie]; - - if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_6) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processNaturalSizeChange:) - name:@"QTMovieNaturalSizeDidChangeNotification" - object:m_movie]; - - } - else { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(processNaturalSizeChange:) - name:QTMovieEditedNotification - object:m_movie]; - } - - [movie retain]; - } -} - -- (void) processEOS:(NSNotification *)notification -{ - Q_UNUSED(notification); - QMetaObject::invokeMethod(m_session, "processEOS", Qt::AutoConnection); -} - -- (void) processLoadStateChange:(NSNotification *)notification -{ - Q_UNUSED(notification); - QMetaObject::invokeMethod(m_session, "processLoadStateChange", Qt::AutoConnection); -} - -- (void) processVolumeChange:(NSNotification *)notification -{ - Q_UNUSED(notification); - QMetaObject::invokeMethod(m_session, "processVolumeChange", Qt::AutoConnection); -} - -- (void) processNaturalSizeChange :(NSNotification *)notification -{ - Q_UNUSED(notification); - QMetaObject::invokeMethod(m_session, "processNaturalSizeChange", Qt::AutoConnection); -} - -- (void) processPositionChange :(NSNotification *)notification -{ - Q_UNUSED(notification); - QMetaObject::invokeMethod(m_session, "processPositionChange", Qt::AutoConnection); -} - -@end - -static inline NSString *qString2CFStringRef(const QString &string) -{ - return [NSString stringWithCharacters:reinterpret_cast(string.unicode()) length:string.length()]; -} - -QT7PlayerSession::QT7PlayerSession(QObject *parent) - : QObject(parent) - , m_QTMovie(0) - , m_state(QMediaPlayer::StoppedState) - , m_mediaStatus(QMediaPlayer::NoMedia) - , m_mediaStream(0) - , m_videoOutput(0) - , m_muted(false) - , m_tryingAsync(false) - , m_volume(100) - , m_rate(1.0) - , m_duration(0) - , m_videoAvailable(false) - , m_audioAvailable(false) -{ - m_movieObserver = [[QTMovieObserver alloc] initWithPlayerSession:this]; -} - -QT7PlayerSession::~QT7PlayerSession() -{ - if (m_videoOutput) - m_videoOutput->setMovie(0); - - [(QTMovieObserver*)m_movieObserver setMovie:nil]; - [(QTMovieObserver*)m_movieObserver release]; - [(QTMovie*)m_QTMovie release]; -} - -void *QT7PlayerSession::movie() const -{ - return m_QTMovie; -} - -void QT7PlayerSession::setVideoOutput(QT7VideoOutput *output) -{ - if (m_videoOutput == output) - return; - - if (m_videoOutput) - m_videoOutput->setMovie(0); - - m_videoOutput = output; - - if (m_videoOutput && m_state != QMediaPlayer::StoppedState) - m_videoOutput->setMovie(m_QTMovie); -} - -qint64 QT7PlayerSession::position() const -{ - if (!m_QTMovie) - return 0; - - QTTime qtTime = [(QTMovie*)m_QTMovie currentTime]; - - return static_cast(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f); -} - -qint64 QT7PlayerSession::duration() const -{ - if (!m_QTMovie) - return 0; - - QTTime qtTime = [(QTMovie*)m_QTMovie duration]; - - return static_cast(float(qtTime.timeValue) / float(qtTime.timeScale) * 1000.0f); -} - -QMediaPlayer::State QT7PlayerSession::state() const -{ - return m_state; -} - -QMediaPlayer::MediaStatus QT7PlayerSession::mediaStatus() const -{ - return m_mediaStatus; -} - -int QT7PlayerSession::bufferStatus() const -{ - return 100; -} - -int QT7PlayerSession::volume() const -{ - return m_volume; -} - -bool QT7PlayerSession::isMuted() const -{ - return m_muted; -} - -bool QT7PlayerSession::isSeekable() const -{ - return true; -} - -#ifndef QUICKTIME_C_API_AVAILABLE -@interface QTMovie(QtExtensions) -- (NSArray*)loadedRanges; -- (QTTime)maxTimeLoaded; -@end -#endif - -QMediaTimeRange QT7PlayerSession::availablePlaybackRanges() const -{ - QTMovie *movie = (QTMovie*)m_QTMovie; -#ifndef QUICKTIME_C_API_AVAILABLE - AutoReleasePool pool; - if ([movie respondsToSelector:@selector(loadedRanges)]) { - QMediaTimeRange rc; - NSArray *r = [movie loadedRanges]; - for (NSValue *tr in r) { - QTTimeRange timeRange = [tr QTTimeRangeValue]; - qint64 startTime = qint64(float(timeRange.time.timeValue) / timeRange.time.timeScale * 1000.0); - rc.addInterval(startTime, startTime + qint64(float(timeRange.duration.timeValue) / timeRange.duration.timeScale * 1000.0)); - } - return rc; - } - else if ([movie respondsToSelector:@selector(maxTimeLoaded)]) { - QTTime loadedTime = [movie maxTimeLoaded]; - return QMediaTimeRange(0, qint64(float(loadedTime.timeValue) / loadedTime.timeScale * 1000.0)); - } -#else - TimeValue loadedTime; - TimeScale scale; - Movie m = [movie quickTimeMovie]; - if (GetMaxLoadedTimeInMovie(m, &loadedTime) == noErr) { - scale = GetMovieTimeScale(m); - return QMediaTimeRange(0, qint64(float(loadedTime) / scale * 1000.0f)); - } -#endif - return QMediaTimeRange(0, duration()); -} - -qreal QT7PlayerSession::playbackRate() const -{ - return m_rate; -} - -void QT7PlayerSession::setPlaybackRate(qreal rate) -{ - if (qFuzzyCompare(m_rate, rate)) - return; - - m_rate = rate; - - if (m_QTMovie != 0 && m_state == QMediaPlayer::PlayingState) { - AutoReleasePool pool; - float preferredRate = [[(QTMovie*)m_QTMovie attributeForKey:@"QTMoviePreferredRateAttribute"] floatValue]; - [(QTMovie*)m_QTMovie setRate:preferredRate * m_rate]; - } -} - -void QT7PlayerSession::setPosition(qint64 pos) -{ - if ( !isSeekable() || pos == position()) - return; - - if (duration() > 0) - pos = qMin(pos, duration()); - - QTTime newQTTime = [(QTMovie*)m_QTMovie currentTime]; - newQTTime.timeValue = (pos / 1000.0f) * newQTTime.timeScale; - [(QTMovie*)m_QTMovie setCurrentTime:newQTTime]; - - //reset the EndOfMedia status position is changed after playback is finished - if (m_mediaStatus == QMediaPlayer::EndOfMedia) - processLoadStateChange(); -} - -void QT7PlayerSession::play() -{ - if (m_state == QMediaPlayer::PlayingState) - return; - - m_state = QMediaPlayer::PlayingState; - - if (m_videoOutput) - m_videoOutput->setMovie(m_QTMovie); - - //reset the EndOfMedia status if the same file is played again - if (m_mediaStatus == QMediaPlayer::EndOfMedia) - processLoadStateChange(); - - AutoReleasePool pool; - float preferredRate = [[(QTMovie*)m_QTMovie attributeForKey:@"QTMoviePreferredRateAttribute"] floatValue]; - [(QTMovie*)m_QTMovie setRate:preferredRate * m_rate]; - - processLoadStateChange(); - Q_EMIT stateChanged(m_state); -} - -void QT7PlayerSession::pause() -{ - if (m_state == QMediaPlayer::PausedState) - return; - - m_state = QMediaPlayer::PausedState; - - if (m_videoOutput) - m_videoOutput->setMovie(m_QTMovie); - - //reset the EndOfMedia status if the same file is played again - if (m_mediaStatus == QMediaPlayer::EndOfMedia) - processLoadStateChange(); - - [(QTMovie*)m_QTMovie setRate:0]; - - processLoadStateChange(); - Q_EMIT stateChanged(m_state); -} - -void QT7PlayerSession::stop() -{ - if (m_state == QMediaPlayer::StoppedState) - return; - - m_state = QMediaPlayer::StoppedState; - - [(QTMovie*)m_QTMovie setRate:0]; - setPosition(0); - - if (m_videoOutput) - m_videoOutput->setMovie(0); - - processLoadStateChange(); - Q_EMIT stateChanged(m_state); - Q_EMIT positionChanged(position()); -} - -void QT7PlayerSession::setVolume(int volume) -{ - if (m_volume == volume) - return; - - m_volume = volume; - - if (m_QTMovie != 0) - [(QTMovie*)m_QTMovie setVolume:m_volume / 100.0f]; - - Q_EMIT volumeChanged(m_volume); -} - -void QT7PlayerSession::setMuted(bool muted) -{ - if (m_muted == muted) - return; - - m_muted = muted; - - if (m_QTMovie != 0) - [(QTMovie*)m_QTMovie setMuted:m_muted]; - - Q_EMIT mutedChanged(muted); -} - -QMediaContent QT7PlayerSession::media() const -{ - return m_resources; -} - -const QIODevice *QT7PlayerSession::mediaStream() const -{ - return m_mediaStream; -} - -void QT7PlayerSession::setMedia(const QMediaContent &content, QIODevice *stream) -{ - AutoReleasePool pool; - -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO << content.canonicalUrl(); -#endif - - if (m_QTMovie) { - [(QTMovieObserver*)m_movieObserver setMovie:nil]; - - if (m_videoOutput) - m_videoOutput->setMovie(0); - - [(QTMovie*)m_QTMovie release]; - m_QTMovie = 0; - m_resourceHandler.clear(); - } - - m_resources = content; - m_mediaStream = stream; - QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatus; - - if (content.isNull()) { - m_mediaStatus = QMediaPlayer::NoMedia; - if (m_state != QMediaPlayer::StoppedState) - Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); - - if (m_mediaStatus != oldMediaStatus) - Q_EMIT mediaStatusChanged(m_mediaStatus); - Q_EMIT positionChanged(position()); - return; - } - - m_mediaStatus = QMediaPlayer::LoadingMedia; - if (m_mediaStatus != oldMediaStatus) - Q_EMIT mediaStatusChanged(m_mediaStatus); - - QNetworkRequest request = content.canonicalResource().request(); - - QVariant cookies = request.header(QNetworkRequest::CookieHeader); - if (cookies.isValid()) { - NSHTTPCookieStorage *store = [NSHTTPCookieStorage sharedHTTPCookieStorage]; - QList cookieList = cookies.value >(); - - Q_FOREACH (const QNetworkCookie &requestCookie, cookieList) { - NSMutableDictionary *p = [NSMutableDictionary dictionaryWithObjectsAndKeys: - qString2CFStringRef(requestCookie.name()), NSHTTPCookieName, - qString2CFStringRef(requestCookie.value()), NSHTTPCookieValue, - qString2CFStringRef(requestCookie.domain()), NSHTTPCookieDomain, - qString2CFStringRef(requestCookie.path()), NSHTTPCookiePath, - nil - ]; - if (requestCookie.isSessionCookie()) - [p setObject:[NSString stringWithUTF8String:"TRUE"] forKey:NSHTTPCookieDiscard]; - else - [p setObject:[NSDate dateWithTimeIntervalSince1970:requestCookie.expirationDate().toTime_t()] forKey:NSHTTPCookieExpires]; - - [store setCookie:[NSHTTPCookie cookieWithProperties:p]]; - } - } - - // Attempt multiple times to open the movie. - // First try - attempt open in async mode - openMovie(true); - - Q_EMIT positionChanged(position()); -} - -void QT7PlayerSession::openMovie(bool tryAsync) -{ - QUrl requestUrl = m_resources.canonicalResource().request().url(); - if (requestUrl.scheme().isEmpty()) - requestUrl.setScheme(QLatin1String("file")); - -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO << requestUrl; -#endif - - NSError *err = 0; - NSString *urlString = [NSString stringWithUTF8String:requestUrl.toEncoded().constData()]; - - NSMutableDictionary *attr = [NSMutableDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithBool:YES], QTMovieOpenAsyncOKAttribute, - [NSNumber numberWithBool:YES], QTMovieIsActiveAttribute, - [NSNumber numberWithBool:YES], QTMovieResolveDataRefsAttribute, - [NSNumber numberWithBool:YES], QTMovieDontInteractWithUserAttribute, - nil]; - - - if (requestUrl.scheme() == QLatin1String("qrc")) { - // Load from Qt resource - m_resourceHandler.setResourceFile(QLatin1Char(':') + requestUrl.path()); - if (!m_resourceHandler.isValid()) { - Q_EMIT error(QMediaPlayer::FormatError, tr("Attempting to play invalid Qt resource")); - return; - } - - CFDataRef resourceData = - CFDataCreateWithBytesNoCopy(0, m_resourceHandler.data(), m_resourceHandler.size(), kCFAllocatorNull); - - QTDataReference *dataReference = - [QTDataReference dataReferenceWithReferenceToData:(NSData*)resourceData - name:qString2CFStringRef(requestUrl.path()) - MIMEType:nil]; - - [attr setObject:dataReference forKey:QTMovieDataReferenceAttribute]; - - CFRelease(resourceData); - } else { - [attr setObject:[NSURL URLWithString:urlString] forKey:QTMovieURLAttribute]; - } - - if (tryAsync && QSysInfo::MacintoshVersion >= QSysInfo::MV_10_6) { - [attr setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenAsyncRequiredAttribute"]; -// XXX: This is disabled for now. causes some problems with video playback for some formats -// [attr setObject:[NSNumber numberWithBool:YES] forKey:@"QTMovieOpenForPlaybackAttribute"]; - m_tryingAsync = true; - } - else - m_tryingAsync = false; - - m_QTMovie = [QTMovie movieWithAttributes:attr error:&err]; - if (err != nil) { - // First attempt to test for inability to perform async -// if ([err code] == QTErrorMovieOpeningCannotBeAsynchronous) { XXX: error code unknown! - if (m_tryingAsync) { - m_tryingAsync = false; - err = nil; - [attr removeObjectForKey:@"QTMovieOpenAsyncRequiredAttribute"]; - m_QTMovie = [QTMovie movieWithAttributes:attr error:&err]; - } - } - - if (err != nil) { - m_QTMovie = 0; - QString description = QString::fromUtf8([[err localizedDescription] UTF8String]); - Q_EMIT error(QMediaPlayer::FormatError, description); - -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO << description; -#endif - } - else { - [(QTMovie*)m_QTMovie retain]; - - [(QTMovieObserver*)m_movieObserver setMovie:(QTMovie*)m_QTMovie]; - - if (m_state != QMediaPlayer::StoppedState && m_videoOutput) - m_videoOutput->setMovie(m_QTMovie); - - processLoadStateChange(); - - [(QTMovie*)m_QTMovie setMuted:m_muted]; - [(QTMovie*)m_QTMovie setVolume:m_volume / 100.0f]; - } -} - -bool QT7PlayerSession::isAudioAvailable() const -{ - if (!m_QTMovie) - return false; - - AutoReleasePool pool; - return [[(QTMovie*)m_QTMovie attributeForKey:@"QTMovieHasAudioAttribute"] boolValue] == YES; -} - -bool QT7PlayerSession::isVideoAvailable() const -{ - if (!m_QTMovie) - return false; - - AutoReleasePool pool; - return [[(QTMovie*)m_QTMovie attributeForKey:@"QTMovieHasVideoAttribute"] boolValue] == YES; -} - -void QT7PlayerSession::processEOS() -{ -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO; -#endif - Q_EMIT positionChanged(position()); - m_mediaStatus = QMediaPlayer::EndOfMedia; - if (m_videoOutput) - m_videoOutput->setMovie(0); - Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); - Q_EMIT mediaStatusChanged(m_mediaStatus); -} - -void QT7PlayerSession::processLoadStateChange() -{ - if (!m_QTMovie) - return; - - AutoReleasePool pool; - - long state = [[(QTMovie*)m_QTMovie attributeForKey:QTMovieLoadStateAttribute] longValue]; - -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO << state; -#endif - -#ifndef QUICKTIME_C_API_AVAILABLE - enum { - kMovieLoadStateError = -1L, - kMovieLoadStateLoading = 1000, - kMovieLoadStateLoaded = 2000, - kMovieLoadStatePlayable = 10000, - kMovieLoadStatePlaythroughOK = 20000, - kMovieLoadStateComplete = 100000 - }; -#endif - - if (state == kMovieLoadStateError) { - if (m_tryingAsync) { - NSError *error = [(QTMovie*)m_QTMovie attributeForKey:@"QTMovieLoadStateErrorAttribute"]; - if ([error code] == componentNotThreadSafeErr) { - // Last Async check, try again with no such flag - openMovie(false); - } - } - else { - if (m_videoOutput) - m_videoOutput->setMovie(0); - - Q_EMIT error(QMediaPlayer::FormatError, tr("Failed to load media")); - Q_EMIT mediaStatusChanged(m_mediaStatus = QMediaPlayer::InvalidMedia); - Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); - } - - return; - } - - QMediaPlayer::MediaStatus newStatus = QMediaPlayer::NoMedia; - bool isPlaying = (m_state != QMediaPlayer::StoppedState); - - if (state >= kMovieLoadStatePlaythroughOK) { - newStatus = isPlaying ? QMediaPlayer::BufferedMedia : QMediaPlayer::LoadedMedia; - } else if (state >= kMovieLoadStatePlayable) - newStatus = isPlaying ? QMediaPlayer::BufferingMedia : QMediaPlayer::LoadingMedia; - else if (state >= kMovieLoadStateLoading) { - if (!isPlaying) - newStatus = QMediaPlayer::LoadingMedia; - else if (m_mediaStatus >= QMediaPlayer::LoadedMedia) - newStatus = QMediaPlayer::StalledMedia; - else - newStatus = QMediaPlayer::LoadingMedia; - } - - if (state >= kMovieLoadStatePlayable && - m_state == QMediaPlayer::PlayingState && - [(QTMovie*)m_QTMovie rate] == 0) { - - float preferredRate = [[(QTMovie*)m_QTMovie attributeForKey:@"QTMoviePreferredRateAttribute"] floatValue]; - - [(QTMovie*)m_QTMovie setRate:preferredRate * m_rate]; - } - - if (state >= kMovieLoadStateLoaded) { - qint64 currentDuration = duration(); - if (m_duration != currentDuration) - Q_EMIT durationChanged(m_duration = currentDuration); - - if (m_audioAvailable != isAudioAvailable()) - Q_EMIT audioAvailableChanged(m_audioAvailable = !m_audioAvailable); - - if (m_videoAvailable != isVideoAvailable()) - Q_EMIT videoAvailableChanged(m_videoAvailable = !m_videoAvailable); - } - - if (newStatus != m_mediaStatus) - Q_EMIT mediaStatusChanged(m_mediaStatus = newStatus); -} - -void QT7PlayerSession::processVolumeChange() -{ - if (!m_QTMovie) - return; - - int newVolume = qRound(100.0f * [((QTMovie*)m_QTMovie) volume]); - - if (newVolume != m_volume) { - Q_EMIT volumeChanged(m_volume = newVolume); - } -} - -void QT7PlayerSession::processNaturalSizeChange() -{ - AutoReleasePool pool; - NSSize size = [[(QTMovie*)m_QTMovie attributeForKey:@"QTMovieNaturalSizeAttribute"] sizeValue]; -#ifdef QT_DEBUG_QT7 - qDebug() << Q_FUNC_INFO << QSize(size.width, size.height); -#endif - - if (m_videoOutput) - m_videoOutput->updateNaturalSize(QSize(size.width, size.height)); -} - -void QT7PlayerSession::processPositionChange() -{ - Q_EMIT positionChanged(position()); -} - -#include "moc_qt7playersession.cpp" diff --git a/src/plugins/qt7/qcvdisplaylink.h b/src/plugins/qt7/qcvdisplaylink.h deleted file mode 100644 index ec24a0aa..00000000 --- a/src/plugins/qt7/qcvdisplaylink.h +++ /dev/null @@ -1,80 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QCVDISPLAYLINK_H -#define QCVDISPLAYLINK_H - -#include -#include - -#include - -#include - -QT_BEGIN_NAMESPACE - -class QCvDisplayLink : public QObject -{ -Q_OBJECT -public: - QCvDisplayLink(QObject *parent = 0); - virtual ~QCvDisplayLink(); - - bool isValid(); - 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 *); - -private: - CVDisplayLinkRef m_displayLink; - QMutex m_displayLinkMutex; - bool m_pendingDisplayLinkEvent; - bool m_isActive; - CVTimeStamp m_frameTimeStamp; -}; - -QT_END_NAMESPACE - -#endif - diff --git a/src/plugins/qt7/qcvdisplaylink.mm b/src/plugins/qt7/qcvdisplaylink.mm deleted file mode 100644 index 89095549..00000000 --- a/src/plugins/qt7/qcvdisplaylink.mm +++ /dev/null @@ -1,156 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qcvdisplaylink.h" - -#include -#include - -QT_USE_NAMESPACE - -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); - - QCvDisplayLink *link = (QCvDisplayLink *)displayLinkContext; - - link->displayLinkEvent(inOutputTime); - return kCVReturnSuccess; -} - - -QCvDisplayLink::QCvDisplayLink(QObject *parent) - :QObject(parent), - m_pendingDisplayLinkEvent(false), - m_isActive(false) -{ - // 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); - } -} - -QCvDisplayLink::~QCvDisplayLink() -{ - if (m_displayLink) { - CVDisplayLinkStop(m_displayLink); - CVDisplayLinkRelease(m_displayLink); - m_displayLink = NULL; - } -} - -bool QCvDisplayLink::isValid() -{ - return m_displayLink != 0; -} - -bool QCvDisplayLink::isActive() const -{ - return m_isActive; -} - -void QCvDisplayLink::start() -{ - if (m_displayLink && !m_isActive) { - CVDisplayLinkStart(m_displayLink); - m_isActive = true; - } -} - -void QCvDisplayLink::stop() -{ - if (m_displayLink && m_isActive) { - CVDisplayLinkStop(m_displayLink); - m_isActive = false; - } -} - -void QCvDisplayLink::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; - m_frameTimeStamp = *ts; - m_displayLinkMutex.unlock(); - - if (!pending) - qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority); -} - -bool QCvDisplayLink::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); -} - -#include "moc_qcvdisplaylink.cpp" - diff --git a/src/plugins/qt7/qt7.json b/src/plugins/qt7/qt7.json deleted file mode 100644 index b4cebad9..00000000 --- a/src/plugins/qt7/qt7.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "Keys": ["qt7"], - "Services": ["org.qt-project.qt.mediaplayer"] -} diff --git a/src/plugins/qt7/qt7.pro b/src/plugins/qt7/qt7.pro deleted file mode 100644 index b23eba40..00000000 --- a/src/plugins/qt7/qt7.pro +++ /dev/null @@ -1,67 +0,0 @@ -# Avoid clash with a variable named `slots' in a Quartz header -CONFIG += no_keywords - -TARGET = qqt7engine -QT += multimedia-private network -qtHaveModule(widgets) { - QT += multimediawidgets-private widgets -} - -PLUGIN_TYPE = mediaservice -PLUGIN_CLASS_NAME = QT7ServicePlugin -load(qt_plugin) - -!simulator { -QT += opengl -} - -#DEFINES += QT_DEBUG_QT7 - -LIBS += -framework AppKit -framework AudioUnit \ - -framework AudioToolbox -framework CoreAudio \ - -framework QuartzCore -framework QTKit - -# QUICKTIME_C_API_AVAILABLE is true only on i386 -# so make sure to link QuickTime -contains(QT_ARCH, i386) { - LIBS += -framework QuickTime -} - -HEADERS += \ - qt7backend.h \ - qt7videooutput.h \ - qt7serviceplugin.h - -OBJECTIVE_SOURCES += \ - qt7backend.mm \ - qt7serviceplugin.mm - -!simulator { - HEADERS += \ - qt7movieviewoutput.h \ - qt7movierenderer.h \ - qt7ciimagevideobuffer.h \ - qcvdisplaylink.h - - OBJECTIVE_SOURCES += \ - qt7movieviewoutput.mm \ - qt7movierenderer.mm \ - qt7videooutput.mm \ - qt7ciimagevideobuffer.mm \ - qcvdisplaylink.mm - - qtHaveModule(widgets) { - HEADERS += \ - qt7movieviewrenderer.h \ - qt7movievideowidget.h - - OBJECTIVE_SOURCES += \ - qt7movieviewrenderer.mm \ - qt7movievideowidget.mm - } -} - -include(mediaplayer/mediaplayer.pri) - -OTHER_FILES += \ - qt7.json diff --git a/src/plugins/qt7/qt7backend.h b/src/plugins/qt7/qt7backend.h deleted file mode 100644 index e2aa2c5d..00000000 --- a/src/plugins/qt7/qt7backend.h +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7BACKEND_H -#define QT7BACKEND_H - -#include "qtmultimediadefs.h" - -#include - -#ifndef Q_OS_SIMULATOR -#ifndef Q_OS_MAC64 -#define QUICKTIME_C_API_AVAILABLE -#endif -#endif // !defined(Q_WS_SIMULATOR) - -QT_BEGIN_NAMESPACE - -class AutoReleasePool -{ -private: - void *pool; -public: - AutoReleasePool(); - ~AutoReleasePool(); -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7backend.mm b/src/plugins/qt7/qt7backend.mm deleted file mode 100644 index 136220ce..00000000 --- a/src/plugins/qt7/qt7backend.mm +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7backend.h" - -#import -#include - - -QT_BEGIN_NAMESPACE - -AutoReleasePool::AutoReleasePool() -{ - pool = (void*)[[NSAutoreleasePool alloc] init]; -} - -AutoReleasePool::~AutoReleasePool() -{ - [(NSAutoreleasePool*)pool release]; -} - -QT_END_NAMESPACE diff --git a/src/plugins/qt7/qt7ciimagevideobuffer.h b/src/plugins/qt7/qt7ciimagevideobuffer.h deleted file mode 100644 index 2ad7957c..00000000 --- a/src/plugins/qt7/qt7ciimagevideobuffer.h +++ /dev/null @@ -1,78 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7CIIMAGEVIDEOBUFFER_H -#define QT7CIIMAGEVIDEOBUFFER_H - -#include "qt7backend.h" -#import - -#include -#include - - -// -// 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 QT7CIImageVideoBuffer : public QAbstractVideoBuffer -{ -public: - QT7CIImageVideoBuffer(CIImage *image); - - virtual ~QT7CIImageVideoBuffer(); - - MapMode mapMode() const; - uchar *map(MapMode mode, int *numBytes, int *bytesPerLine); - void unmap(); - QVariant handle() const; - -private: - CIImage *m_image; - NSBitmapImageRep *m_buffer; - MapMode m_mode; -}; - - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7ciimagevideobuffer.mm b/src/plugins/qt7/qt7ciimagevideobuffer.mm deleted file mode 100644 index adb1a689..00000000 --- a/src/plugins/qt7/qt7ciimagevideobuffer.mm +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7ciimagevideobuffer.h" - -#include -#include - -QT7CIImageVideoBuffer::QT7CIImageVideoBuffer(CIImage *image) - : QAbstractVideoBuffer(CoreImageHandle) - , m_image(image) - , m_buffer(0) - , m_mode(NotMapped) -{ - [m_image retain]; -} - -QT7CIImageVideoBuffer::~QT7CIImageVideoBuffer() -{ - [m_image release]; - [m_buffer release]; -} - -QAbstractVideoBuffer::MapMode QT7CIImageVideoBuffer::mapMode() const -{ - return m_mode; -} - -uchar *QT7CIImageVideoBuffer::map(QAbstractVideoBuffer::MapMode mode, int *numBytes, int *bytesPerLine) -{ - if (mode == NotMapped || m_mode != NotMapped || !m_image) - return 0; - - if (!m_buffer) { - //swap R and B channels - CIFilter *colorSwapFilter = [CIFilter filterWithName: @"CIColorMatrix" keysAndValues: - @"inputImage", m_image, - @"inputRVector", [CIVector vectorWithX: 0 Y: 0 Z: 1 W: 0], - @"inputGVector", [CIVector vectorWithX: 0 Y: 1 Z: 0 W: 0], - @"inputBVector", [CIVector vectorWithX: 1 Y: 0 Z: 0 W: 0], - @"inputAVector", [CIVector vectorWithX: 0 Y: 0 Z: 0 W: 1], - @"inputBiasVector", [CIVector vectorWithX: 0 Y: 0 Z: 0 W: 0], - nil]; - CIImage *img = [colorSwapFilter valueForKey: @"outputImage"]; - - m_buffer = [[NSBitmapImageRep alloc] initWithCIImage:img]; - } - - if (numBytes) - *numBytes = [m_buffer bytesPerPlane]; - - if (bytesPerLine) - *bytesPerLine = [m_buffer bytesPerRow]; - - m_mode = mode; - - return [m_buffer bitmapData]; -} - -void QT7CIImageVideoBuffer::unmap() -{ - m_mode = NotMapped; -} - -QVariant QT7CIImageVideoBuffer::handle() const -{ - return QVariant::fromValue(m_image); -} - diff --git a/src/plugins/qt7/qt7movierenderer.h b/src/plugins/qt7/qt7movierenderer.h deleted file mode 100644 index 8abbaf84..00000000 --- a/src/plugins/qt7/qt7movierenderer.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7MOVIERENDERER_H -#define QT7MOVIERENDERER_H - -#include "qt7backend.h" - -#include -#include - -#include -#include - -#include "qt7videooutput.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -class QGLContext; - -class QCvDisplayLink; -class QT7PlayerSession; -class QT7PlayerService; - -class QT7MovieRenderer : public QT7VideoRendererControl -{ -Q_OBJECT -public: - QT7MovieRenderer(QObject *parent = 0); - virtual ~QT7MovieRenderer(); - - void setMovie(void *movie); - void updateNaturalSize(const QSize &newSize); - - QAbstractVideoSurface *surface() const; - void setSurface(QAbstractVideoSurface *surface); - - QSize nativeSize() const; - -private Q_SLOTS: - void updateVideoFrame(const CVTimeStamp &ts); - -private: - void setupVideoOutput(); - bool createPixelBufferVisualContext(); - bool createGLVisualContext(); - - void *m_movie; - - QMutex m_mutex; - - QCvDisplayLink *m_displayLink; -#ifdef QUICKTIME_C_API_AVAILABLE - QTVisualContextRef m_visualContext; - bool m_usingGLContext; - const QGLContext *m_currentGLContext; - QSize m_pixelBufferContextGeometry; -#endif - QAbstractVideoSurface *m_surface; - QSize m_nativeSize; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7movierenderer.mm b/src/plugins/qt7/qt7movierenderer.mm deleted file mode 100644 index 9110b821..00000000 --- a/src/plugins/qt7/qt7movierenderer.mm +++ /dev/null @@ -1,481 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#import - -#include "qt7backend.h" - -#include "qt7playercontrol.h" -#include "qt7movierenderer.h" -#include "qt7playersession.h" -#include "qt7ciimagevideobuffer.h" -#include "qcvdisplaylink.h" -#include -#include - -#include -#include -#include - -#include - -QT_USE_NAMESPACE - -//#define USE_MAIN_MONITOR_COLOR_SPACE 1 - -class CVGLTextureVideoBuffer : public QAbstractVideoBuffer -{ -public: - CVGLTextureVideoBuffer(CVOpenGLTextureRef buffer) - : QAbstractVideoBuffer(GLTextureHandle) - , m_buffer(buffer) - , m_mode(NotMapped) - { - CVOpenGLTextureRetain(m_buffer); - } - - virtual ~CVGLTextureVideoBuffer() - { - CVOpenGLTextureRelease(m_buffer); - } - - QVariant handle() const - { - GLuint id = CVOpenGLTextureGetName(m_buffer); - return QVariant(int(id)); - } - - MapMode mapMode() const { return m_mode; } - - uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) - { - if (numBytes) - *numBytes = 0; - - if (bytesPerLine) - *bytesPerLine = 0; - - m_mode = mode; - return 0; - } - - void unmap() { m_mode = NotMapped; } - -private: - CVOpenGLTextureRef m_buffer; - MapMode m_mode; -}; - - -class CVPixelBufferVideoBuffer : public QAbstractVideoBuffer -{ -public: - CVPixelBufferVideoBuffer(CVPixelBufferRef buffer) - : QAbstractVideoBuffer(NoHandle) - , m_buffer(buffer) - , m_mode(NotMapped) - { - CVPixelBufferRetain(m_buffer); - } - - virtual ~CVPixelBufferVideoBuffer() - { - CVPixelBufferRelease(m_buffer); - } - - MapMode mapMode() const { return m_mode; } - - uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) - { - if (mode != NotMapped && m_mode == NotMapped) { - CVPixelBufferLockBaseAddress(m_buffer, 0); - - if (numBytes) - *numBytes = CVPixelBufferGetDataSize(m_buffer); - - if (bytesPerLine) - *bytesPerLine = CVPixelBufferGetBytesPerRow(m_buffer); - - m_mode = mode; - - return (uchar*)CVPixelBufferGetBaseAddress(m_buffer); - } else { - return 0; - } - } - - void unmap() - { - if (m_mode != NotMapped) { - m_mode = NotMapped; - CVPixelBufferUnlockBaseAddress(m_buffer, 0); - } - } - -private: - CVPixelBufferRef m_buffer; - MapMode m_mode; -}; - - - -QT7MovieRenderer::QT7MovieRenderer(QObject *parent) - :QT7VideoRendererControl(parent), - m_movie(0), -#ifdef QUICKTIME_C_API_AVAILABLE - m_visualContext(0), - m_usingGLContext(false), - m_currentGLContext(0), -#endif - m_surface(0) -{ -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieRenderer"; -#endif - m_displayLink = new QCvDisplayLink(this); - connect(m_displayLink, SIGNAL(tick(CVTimeStamp)), SLOT(updateVideoFrame(CVTimeStamp))); -} - - -bool QT7MovieRenderer::createGLVisualContext() -{ -#ifdef QUICKTIME_C_API_AVAILABLE - AutoReleasePool pool; - CGLContextObj cglContext = CGLGetCurrentContext(); - NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat]; - CGLPixelFormatObj cglPixelFormat = static_cast([nsglPixelFormat CGLPixelFormatObj]); - - OSStatus err = QTOpenGLTextureContextCreate(kCFAllocatorDefault, cglContext, - cglPixelFormat, NULL, &m_visualContext); - if (err != noErr) - qWarning() << "Could not create visual context (OpenGL)"; - - return (err == noErr); -#endif // QUICKTIME_C_API_AVAILABLE - - return false; -} - -#ifdef QUICKTIME_C_API_AVAILABLE -static bool DictionarySetValue(CFMutableDictionaryRef dict, CFStringRef key, SInt32 value) -{ - CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &value); - - if (number) { - CFDictionarySetValue( dict, key, number ); - CFRelease( number ); - return true; - } - return false; -} -#endif // QUICKTIME_C_API_AVAILABLE - -bool QT7MovieRenderer::createPixelBufferVisualContext() -{ -#ifdef QUICKTIME_C_API_AVAILABLE - if (m_visualContext) { - QTVisualContextRelease(m_visualContext); - m_visualContext = 0; - } - - m_pixelBufferContextGeometry = m_nativeSize; - - CFMutableDictionaryRef pixelBufferOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - //DictionarySetValue(pixelBufferOptions, kCVPixelBufferPixelFormatTypeKey, k32ARGBPixelFormat ); - DictionarySetValue(pixelBufferOptions, kCVPixelBufferPixelFormatTypeKey, k32BGRAPixelFormat ); - DictionarySetValue(pixelBufferOptions, kCVPixelBufferWidthKey, m_nativeSize.width() ); - DictionarySetValue(pixelBufferOptions, kCVPixelBufferHeightKey, m_nativeSize.height() ); - DictionarySetValue(pixelBufferOptions, kCVPixelBufferBytesPerRowAlignmentKey, 16); - //CFDictionarySetValue(pixelBufferOptions, kCVPixelBufferOpenGLCompatibilityKey, kCFBooleanTrue); - - CFMutableDictionaryRef visualContextOptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - CFDictionarySetValue(visualContextOptions, kQTVisualContextPixelBufferAttributesKey, pixelBufferOptions); - - CGColorSpaceRef colorSpace = NULL; - -#if USE_MAIN_MONITOR_COLOR_SPACE - CMProfileRef sysprof = NULL; - - // Get the Systems Profile for the main display - if (CMGetSystemProfile(&sysprof) == noErr) { - // Create a colorspace with the systems profile - colorSpace = CGColorSpaceCreateWithPlatformColorSpace(sysprof); - CMCloseProfile(sysprof); - } -#endif - - if (!colorSpace) - colorSpace = CGColorSpaceCreateDeviceRGB(); - - CFDictionarySetValue(visualContextOptions, kQTVisualContextOutputColorSpaceKey, colorSpace); - - OSStatus err = QTPixelBufferContextCreate(kCFAllocatorDefault, - visualContextOptions, - &m_visualContext); - CFRelease(pixelBufferOptions); - CFRelease(visualContextOptions); - - if (err != noErr) { - qWarning() << "Could not create visual context (PixelBuffer)"; - return false; - } - - return true; -#endif // QUICKTIME_C_API_AVAILABLE - - return false; -} - - -QT7MovieRenderer::~QT7MovieRenderer() -{ - m_displayLink->stop(); -} - -void QT7MovieRenderer::setupVideoOutput() -{ - AutoReleasePool pool; - -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieRenderer::setupVideoOutput" << m_movie; -#endif - - if (m_movie == 0 || m_surface == 0) { - m_displayLink->stop(); - return; - } - - NSSize size = [[(QTMovie*)m_movie attributeForKey:@"QTMovieNaturalSizeAttribute"] sizeValue]; - m_nativeSize = QSize(size.width, size.height); - -#ifdef QUICKTIME_C_API_AVAILABLE - bool usedGLContext = m_usingGLContext; - - if (!m_nativeSize.isEmpty()) { - - bool glSupported = !m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).isEmpty(); - - //Try rendering using opengl textures first: - if (glSupported) { - QVideoSurfaceFormat format(m_nativeSize, QVideoFrame::Format_RGB32, QAbstractVideoBuffer::GLTextureHandle); - - if (m_surface->isActive()) - m_surface->stop(); - - if (!m_surface->start(format)) { - qWarning() << "failed to start video surface" << m_surface->error(); - qWarning() << "Surface format:" << format; - glSupported = false; - } else { - m_usingGLContext = true; - } - - } - - if (!glSupported) { - m_usingGLContext = false; - QVideoSurfaceFormat format(m_nativeSize, QVideoFrame::Format_RGB32); - - if (m_surface->isActive() && m_surface->surfaceFormat() != format) { -#ifdef QT_DEBUG_QT7 - qDebug() << "Surface format was changed, stop the surface."; -#endif - m_surface->stop(); - } - - if (!m_surface->isActive()) { -#ifdef QT_DEBUG_QT7 - qDebug() << "Starting the surface with format" << format; -#endif - if (!m_surface->start(format)) { - qWarning() << "failed to start video surface" << m_surface->error(); - qWarning() << "Surface format:" << format; - } - } - } - } - - - if (m_visualContext) { - //check if the visual context still can be reused - if (usedGLContext != m_usingGLContext || - (m_usingGLContext && (m_currentGLContext != QGLContext::currentContext())) || - (!m_usingGLContext && (m_pixelBufferContextGeometry != m_nativeSize))) { - QTVisualContextRelease(m_visualContext); - m_pixelBufferContextGeometry = QSize(); - m_visualContext = 0; - } - } - - if (!m_nativeSize.isEmpty()) { - if (!m_visualContext) { - if (m_usingGLContext) { -#ifdef QT_DEBUG_QT7 - qDebug() << "Building OpenGL visual context" << m_nativeSize; -#endif - m_currentGLContext = QGLContext::currentContext(); - if (!createGLVisualContext()) { - qWarning() << "QT7MovieRenderer: failed to create visual context"; - return; - } - } else { -#ifdef QT_DEBUG_QT7 - qDebug() << "Building Pixel Buffer visual context" << m_nativeSize; -#endif - if (!createPixelBufferVisualContext()) { - qWarning() << "QT7MovieRenderer: failed to create visual context"; - return; - } - } - } - - // targets a Movie to render into a visual context - SetMovieVisualContext([(QTMovie*)m_movie quickTimeMovie], m_visualContext); - - m_displayLink->start(); - } -#endif - -} - -void QT7MovieRenderer::setMovie(void *movie) -{ -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieRenderer::setMovie" << movie; -#endif - -#ifdef QUICKTIME_C_API_AVAILABLE - QMutexLocker locker(&m_mutex); - - if (m_movie != movie) { - if (m_movie) { - //ensure the old movie doesn't hold the visual context, otherwise it can't be reused - SetMovieVisualContext([(QTMovie*)m_movie quickTimeMovie], nil); - [(QTMovie*)m_movie release]; - } - - m_movie = movie; - [(QTMovie*)m_movie retain]; - - setupVideoOutput(); - } -#else - Q_UNUSED(movie); -#endif -} - -void QT7MovieRenderer::updateNaturalSize(const QSize &newSize) -{ - if (m_nativeSize != newSize) { - m_nativeSize = newSize; - setupVideoOutput(); - } -} - -QAbstractVideoSurface *QT7MovieRenderer::surface() const -{ - return m_surface; -} - -void QT7MovieRenderer::setSurface(QAbstractVideoSurface *surface) -{ -#ifdef QT_DEBUG_QT7 - qDebug() << "Set video surface" << surface; -#endif - - if (surface == m_surface) - return; - - QMutexLocker locker(&m_mutex); - - if (m_surface && m_surface->isActive()) - m_surface->stop(); - - m_surface = surface; - setupVideoOutput(); -} - - -QSize QT7MovieRenderer::nativeSize() const -{ - return m_nativeSize; -} - -void QT7MovieRenderer::updateVideoFrame(const CVTimeStamp &ts) -{ -#ifdef QUICKTIME_C_API_AVAILABLE - - QMutexLocker locker(&m_mutex); - - if (m_surface && m_surface->isActive() && - m_visualContext && QTVisualContextIsNewImageAvailable(m_visualContext, &ts)) { - - CVImageBufferRef imageBuffer = NULL; - - OSStatus status = QTVisualContextCopyImageForTime(m_visualContext, NULL, &ts, &imageBuffer); - - if (status == noErr && imageBuffer) { - QAbstractVideoBuffer *buffer = 0; - - if (m_usingGLContext) { - buffer = new QT7CIImageVideoBuffer([CIImage imageWithCVImageBuffer:imageBuffer]); - CVOpenGLTextureRelease((CVOpenGLTextureRef)imageBuffer); - } else { - buffer = new CVPixelBufferVideoBuffer((CVPixelBufferRef)imageBuffer); - //buffer = new QT7CIImageVideoBuffer( [CIImage imageWithCVImageBuffer:imageBuffer] ); - CVPixelBufferRelease((CVPixelBufferRef)imageBuffer); - } - - QVideoFrame frame(buffer, m_nativeSize, QVideoFrame::Format_RGB32); - m_surface->present(frame); - QTVisualContextTask(m_visualContext); - } - } -#else - Q_UNUSED(ts); -#endif -} - -#include "moc_qt7movierenderer.cpp" diff --git a/src/plugins/qt7/qt7movievideowidget.h b/src/plugins/qt7/qt7movievideowidget.h deleted file mode 100644 index 65503769..00000000 --- a/src/plugins/qt7/qt7movievideowidget.h +++ /dev/null @@ -1,117 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7MOVIEVIDEOWIDGET_H -#define QT7MOVIEVIDEOWIDGET_H - -#include -#include - -#include -#include - -#include "qt7videooutput.h" - -#include -#include - -class GLVideoWidget; - -QT_BEGIN_NAMESPACE - -class QCvDisplayLink; -class QT7PlayerSession; -class QT7PlayerService; - -class QT7MovieVideoWidget : public QT7VideoWidgetControl -{ -Q_OBJECT -public: - QT7MovieVideoWidget(QObject *parent = 0); - virtual ~QT7MovieVideoWidget(); - - void setMovie(void *movie); - void updateNaturalSize(const QSize &newSize); - - QWidget *videoWidget(); - - bool isFullScreen() const; - void setFullScreen(bool fullScreen); - - QSize nativeSize() const; - - Qt::AspectRatioMode aspectRatioMode() const; - void setAspectRatioMode(Qt::AspectRatioMode mode); - - int brightness() const; - void setBrightness(int brightness); - - int contrast() const; - void setContrast(int contrast); - - int hue() const; - void setHue(int hue); - - int saturation() const; - void setSaturation(int saturation); - -private Q_SLOTS: - void updateVideoFrame(const CVTimeStamp &ts); - -private: - void setupVideoOutput(); - bool createVisualContext(); - - void updateColors(); - - void *m_movie; - GLVideoWidget *m_videoWidget; - - QCvDisplayLink *m_displayLink; - -#ifdef QUICKTIME_C_API_AVAILABLE - QTVisualContextRef m_visualContext; -#endif - - bool m_fullscreen; - QSize m_nativeSize; - Qt::AspectRatioMode m_aspectRatioMode; - int m_brightness; - int m_contrast; - int m_hue; - int m_saturation; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7movievideowidget.mm b/src/plugins/qt7/qt7movievideowidget.mm deleted file mode 100644 index ec9367bc..00000000 --- a/src/plugins/qt7/qt7movievideowidget.mm +++ /dev/null @@ -1,437 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7backend.h" - -#import -#import -#import -#import -#import -#import - - -#include "qt7playercontrol.h" -#include "qt7movievideowidget.h" -#include "qt7playersession.h" -#include "qcvdisplaylink.h" -#include -#include - -#include - -#include - -#import - -#include "math.h" - -QT_USE_NAMESPACE - -class GLVideoWidget : public QGLWidget -{ -public: - - GLVideoWidget(QWidget *parent, const QGLFormat &format) - : QGLWidget(format, parent), - m_texRef(0), - m_nativeSize(640,480), - m_aspectRatioMode(Qt::KeepAspectRatio) - { - setAutoFillBackground(false); - } - - void initializeGL() - { - QColor bgColor = palette().color(QPalette::Background); - glClearColor(bgColor.redF(), bgColor.greenF(), bgColor.blueF(), bgColor.alphaF()); - } - - void resizeGL(int w, int h) - { - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glViewport(0, 0, GLsizei(w), GLsizei(h)); - gluOrtho2D(0, GLsizei(w), 0, GLsizei(h)); - updateGL(); - } - - void paintGL() - { - glClear(GL_COLOR_BUFFER_BIT); - if (!m_texRef) - return; - - glPushMatrix(); - glDisable(GL_CULL_FACE); - GLenum target = CVOpenGLTextureGetTarget(m_texRef); - glEnable(target); - - glBindTexture(target, CVOpenGLTextureGetName(m_texRef)); - glTexParameterf(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - GLfloat lowerLeft[2], lowerRight[2], upperRight[2], upperLeft[2]; - CVOpenGLTextureGetCleanTexCoords(m_texRef, lowerLeft, lowerRight, upperRight, upperLeft); - - glBegin(GL_QUADS); - QRect rect = displayRect(); - glTexCoord2f(lowerLeft[0], lowerLeft[1]); - glVertex2i(rect.topLeft().x(), rect.topLeft().y()); - glTexCoord2f(lowerRight[0], lowerRight[1]); - glVertex2i(rect.topRight().x() + 1, rect.topRight().y()); - glTexCoord2f(upperRight[0], upperRight[1]); - glVertex2i(rect.bottomRight().x() + 1, rect.bottomRight().y() + 1); - glTexCoord2f(upperLeft[0], upperLeft[1]); - glVertex2i(rect.bottomLeft().x(), rect.bottomLeft().y() + 1); - glEnd(); - glPopMatrix(); - } - - void setCVTexture(CVOpenGLTextureRef texRef) - { - if (m_texRef) - CVOpenGLTextureRelease(m_texRef); - - m_texRef = texRef; - - if (m_texRef) - CVOpenGLTextureRetain(m_texRef); - - if (isVisible()) { - makeCurrent(); - paintGL(); - swapBuffers(); - } - } - - QSize sizeHint() const - { - return m_nativeSize; - } - - void setNativeSize(const QSize &size) - { - m_nativeSize = size; - } - - void setAspectRatioMode(Qt::AspectRatioMode mode) - { - if (m_aspectRatioMode != mode) { - m_aspectRatioMode = mode; - update(); - } - } - -private: - QRect displayRect() const - { - QRect displayRect = rect(); - - if (m_aspectRatioMode == Qt::KeepAspectRatio) { - QSize size = m_nativeSize; - size.scale(displayRect.size(), Qt::KeepAspectRatio); - - displayRect = QRect(QPoint(0, 0), size); - displayRect.moveCenter(rect().center()); - } - return displayRect; - } - - CVOpenGLTextureRef m_texRef; - QSize m_nativeSize; - Qt::AspectRatioMode m_aspectRatioMode; -}; - -QT7MovieVideoWidget::QT7MovieVideoWidget(QObject *parent) - :QT7VideoWidgetControl(parent), - m_movie(0), - m_videoWidget(0), - m_fullscreen(false), - m_aspectRatioMode(Qt::KeepAspectRatio), - m_brightness(0), - m_contrast(0), - m_hue(0), - m_saturation(0) -{ -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieVideoWidget"; -#endif - - QGLFormat format = QGLFormat::defaultFormat(); - format.setSwapInterval(1); // Vertical sync (avoid tearing) - m_videoWidget = new GLVideoWidget(0, format); - - m_displayLink = new QCvDisplayLink(this); - - connect(m_displayLink, SIGNAL(tick(CVTimeStamp)), SLOT(updateVideoFrame(CVTimeStamp))); - - if (!createVisualContext()) { - qWarning() << "QT7MovieVideoWidget: failed to create visual context"; - } -} - -bool QT7MovieVideoWidget::createVisualContext() -{ -#ifdef QUICKTIME_C_API_AVAILABLE - m_videoWidget->makeCurrent(); - - AutoReleasePool pool; - CGLContextObj cglContext = CGLGetCurrentContext(); - NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat]; - CGLPixelFormatObj cglPixelFormat = static_cast([nsglPixelFormat CGLPixelFormatObj]); - - CFTypeRef keys[] = { kQTVisualContextOutputColorSpaceKey }; - CGColorSpaceRef colorSpace = NULL; - CMProfileRef sysprof = NULL; - - // Get the Systems Profile for the main display - if (CMGetSystemProfile(&sysprof) == noErr) { - // Create a colorspace with the systems profile - colorSpace = CGColorSpaceCreateWithPlatformColorSpace(sysprof); - CMCloseProfile(sysprof); - } - - if (!colorSpace) - colorSpace = CGColorSpaceCreateDeviceRGB(); - - CFDictionaryRef textureContextAttributes = CFDictionaryCreate(kCFAllocatorDefault, - (const void **)keys, - (const void **)&colorSpace, 1, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - - OSStatus err = QTOpenGLTextureContextCreate(kCFAllocatorDefault, - cglContext, - cglPixelFormat, - textureContextAttributes, - &m_visualContext); - if (err != noErr) - qWarning() << "Could not create visual context (OpenGL)"; - - - return (err == noErr); -#endif // QUICKTIME_C_API_AVAILABLE - - return false; -} - -QT7MovieVideoWidget::~QT7MovieVideoWidget() -{ - m_displayLink->stop(); - [(QTMovie*)m_movie release]; - delete m_videoWidget; -} - -QWidget *QT7MovieVideoWidget::videoWidget() -{ - return m_videoWidget; -} - -void QT7MovieVideoWidget::setupVideoOutput() -{ - AutoReleasePool pool; - -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieVideoWidget::setupVideoOutput" << m_movie; -#endif - - if (m_movie == 0) { - m_displayLink->stop(); - return; - } - - NSSize size = [[(QTMovie*)m_movie attributeForKey:@"QTMovieNaturalSizeAttribute"] sizeValue]; - m_nativeSize = QSize(size.width, size.height); - m_videoWidget->setNativeSize(m_nativeSize); - -#ifdef QUICKTIME_C_API_AVAILABLE - // targets a Movie to render into a visual context - SetMovieVisualContext([(QTMovie*)m_movie quickTimeMovie], m_visualContext); -#endif - - m_displayLink->start(); -} - -void QT7MovieVideoWidget::setMovie(void *movie) -{ - if (m_movie == movie) - return; - - if (m_movie) { -#ifdef QUICKTIME_C_API_AVAILABLE - SetMovieVisualContext([(QTMovie*)m_movie quickTimeMovie], nil); -#endif - [(QTMovie*)m_movie release]; - } - - m_movie = movie; - [(QTMovie*)m_movie retain]; - - setupVideoOutput(); -} - -void QT7MovieVideoWidget::updateNaturalSize(const QSize &newSize) -{ - if (m_nativeSize != newSize) { - m_nativeSize = newSize; - setupVideoOutput(); - } -} - -bool QT7MovieVideoWidget::isFullScreen() const -{ - return m_fullscreen; -} - -void QT7MovieVideoWidget::setFullScreen(bool fullScreen) -{ - m_fullscreen = fullScreen; -} - -QSize QT7MovieVideoWidget::nativeSize() const -{ - return m_nativeSize; -} - -Qt::AspectRatioMode QT7MovieVideoWidget::aspectRatioMode() const -{ - return m_aspectRatioMode; -} - -void QT7MovieVideoWidget::setAspectRatioMode(Qt::AspectRatioMode mode) -{ - m_aspectRatioMode = mode; - m_videoWidget->setAspectRatioMode(mode); -} - -int QT7MovieVideoWidget::brightness() const -{ - return m_brightness; -} - -void QT7MovieVideoWidget::setBrightness(int brightness) -{ - m_brightness = brightness; - updateColors(); -} - -int QT7MovieVideoWidget::contrast() const -{ - return m_contrast; -} - -void QT7MovieVideoWidget::setContrast(int contrast) -{ - m_contrast = contrast; - updateColors(); -} - -int QT7MovieVideoWidget::hue() const -{ - return m_hue; -} - -void QT7MovieVideoWidget::setHue(int hue) -{ - m_hue = hue; - updateColors(); -} - -int QT7MovieVideoWidget::saturation() const -{ - return m_saturation; -} - -void QT7MovieVideoWidget::setSaturation(int saturation) -{ - m_saturation = saturation; - updateColors(); -} - -void QT7MovieVideoWidget::updateColors() -{ -#ifdef QUICKTIME_C_API_AVAILABLE - if (m_movie) { - QTMovie *movie = (QTMovie*)m_movie; - - Float32 value; - value = m_brightness/100.0; - SetMovieVisualBrightness([movie quickTimeMovie], value, 0); - value = pow(2, m_contrast/50.0); - SetMovieVisualContrast([movie quickTimeMovie], value, 0); - value = m_hue/100.0; - SetMovieVisualHue([movie quickTimeMovie], value, 0); - value = 1.0+m_saturation/100.0; - SetMovieVisualSaturation([movie quickTimeMovie], value, 0); - } -#endif -} - -void QT7MovieVideoWidget::updateVideoFrame(const CVTimeStamp &ts) -{ -#ifdef QUICKTIME_C_API_AVAILABLE - AutoReleasePool pool; - // check for new frame - if (m_visualContext && QTVisualContextIsNewImageAvailable(m_visualContext, &ts)) { - CVOpenGLTextureRef currentFrame = NULL; - - // get a "frame" (image buffer) from the Visual Context, indexed by the provided time - OSStatus status = QTVisualContextCopyImageForTime(m_visualContext, NULL, &ts, ¤tFrame); - - // the above call may produce a null frame so check for this first - // if we have a frame, then draw it - if (status == noErr && currentFrame) { -#ifdef QT_DEBUG_QT7 - qDebug() << "render video frame"; -#endif - m_videoWidget->setCVTexture(currentFrame); - CVOpenGLTextureRelease(currentFrame); - } - QTVisualContextTask(m_visualContext); - } -#else - Q_UNUSED(ts); -#endif -} - -#include "moc_qt7movievideowidget.cpp" diff --git a/src/plugins/qt7/qt7movieviewoutput.h b/src/plugins/qt7/qt7movieviewoutput.h deleted file mode 100644 index 874de1a0..00000000 --- a/src/plugins/qt7/qt7movieviewoutput.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7MOVIEVIEWOUTPUT_H -#define QT7MOVIEVIEWOUTPUT_H - -#include - -#include -#include - -#include "qt7videooutput.h" - - -QT_BEGIN_NAMESPACE - -class QT7PlayerSession; -class QT7PlayerService; - -class QT7MovieViewOutput : public QT7VideoWindowControl -{ -public: - QT7MovieViewOutput(QObject *parent = 0); - ~QT7MovieViewOutput(); - - void setMovie(void *movie); - void updateNaturalSize(const QSize &newSize); - - WId winId() const; - void setWinId(WId id); - - QRect displayRect() const; - void setDisplayRect(const QRect &rect); - - bool isFullScreen() const; - void setFullScreen(bool fullScreen); - - void repaint(); - - QSize nativeSize() const; - - Qt::AspectRatioMode aspectRatioMode() const; - void setAspectRatioMode(Qt::AspectRatioMode mode); - - int brightness() const; - void setBrightness(int brightness); - - int contrast() const; - void setContrast(int contrast); - - int hue() const; - void setHue(int hue); - - int saturation() const; - void setSaturation(int saturation); - -private: - void setupVideoOutput(); - - void *m_movie; - void *m_movieView; - bool m_layouted; - - WId m_winId; - QRect m_displayRect; - bool m_fullscreen; - QSize m_nativeSize; - Qt::AspectRatioMode m_aspectRatioMode; - int m_brightness; - int m_contrast; - int m_hue; - int m_saturation; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7movieviewoutput.mm b/src/plugins/qt7/qt7movieviewoutput.mm deleted file mode 100644 index 4fcf01d3..00000000 --- a/src/plugins/qt7/qt7movieviewoutput.mm +++ /dev/null @@ -1,339 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#import - -#include "qt7backend.h" - -#include "qt7playercontrol.h" -#include "qt7movieviewoutput.h" -#include "qt7playersession.h" -#include - -#include -#include - -QT_USE_NAMESPACE - -#define VIDEO_TRANSPARENT(m) -(void)m:(NSEvent *)e{[[self superview] m:e];} - -@interface TransparentQTMovieView : QTMovieView -{ -@private - QRect *m_drawRect; - qreal m_brightness, m_contrast, m_saturation, m_hue; -} - -- (TransparentQTMovieView *) init; -- (void) setDrawRect:(QRect &)rect; -- (CIImage *) view:(QTMovieView *)view willDisplayImage:(CIImage *)img; -- (void) setContrast:(qreal) contrast; -@end - -@implementation TransparentQTMovieView - -- (TransparentQTMovieView *) init -{ - self = [super initWithFrame:NSZeroRect]; - if (self) { - [self setControllerVisible:NO]; - [self setContrast:1.0]; - [self setDelegate:self]; - } - return self; -} - -- (void) dealloc -{ - [super dealloc]; -} - -- (void) setContrast:(qreal) contrast -{ - m_hue = 0.0; - m_brightness = 0.0; - m_contrast = contrast; - m_saturation = 1.0; -} - - -- (void) setDrawRect:(QRect &)rect -{ - *m_drawRect = rect; - - NSRect nsrect; - nsrect.origin.x = m_drawRect->x(); - nsrect.origin.y = m_drawRect->y(); - nsrect.size.width = m_drawRect->width(); - nsrect.size.height = m_drawRect->height(); - [self setFrame:nsrect]; -} - -- (CIImage *) view:(QTMovieView *)view willDisplayImage:(CIImage *)img -{ - // This method is called from QTMovieView just - // before the image will be drawn. - Q_UNUSED(view); - - if ( !qFuzzyCompare(m_brightness, 0.0) || - !qFuzzyCompare(m_contrast, 1.0) || - !qFuzzyCompare(m_saturation, 1.0)){ - CIFilter *colorFilter = [CIFilter filterWithName:@"CIColorControls"]; - [colorFilter setValue:[NSNumber numberWithFloat:m_brightness] forKey:@"inputBrightness"]; - [colorFilter setValue:[NSNumber numberWithFloat:(m_contrast < 1) ? m_contrast : 1 + ((m_contrast-1)*3)] forKey:@"inputContrast"]; - [colorFilter setValue:[NSNumber numberWithFloat:m_saturation] forKey:@"inputSaturation"]; - [colorFilter setValue:img forKey:@"inputImage"]; - img = [colorFilter valueForKey:@"outputImage"]; - } - - /*if (m_hue){ - CIFilter *colorFilter = [CIFilter filterWithName:@"CIHueAdjust"]; - [colorFilter setValue:[NSNumber numberWithFloat:(m_hue * 3.14)] forKey:@"inputAngle"]; - [colorFilter setValue:img forKey:@"inputImage"]; - img = [colorFilter valueForKey:@"outputImage"]; - }*/ - - return img; -} - - -VIDEO_TRANSPARENT(mouseDown); -VIDEO_TRANSPARENT(mouseDragged); -VIDEO_TRANSPARENT(mouseUp); -VIDEO_TRANSPARENT(mouseMoved); -VIDEO_TRANSPARENT(mouseEntered); -VIDEO_TRANSPARENT(mouseExited); -VIDEO_TRANSPARENT(rightMouseDown); -VIDEO_TRANSPARENT(rightMouseDragged); -VIDEO_TRANSPARENT(rightMouseUp); -VIDEO_TRANSPARENT(otherMouseDown); -VIDEO_TRANSPARENT(otherMouseDragged); -VIDEO_TRANSPARENT(otherMouseUp); -VIDEO_TRANSPARENT(keyDown); -VIDEO_TRANSPARENT(keyUp); -VIDEO_TRANSPARENT(scrollWheel) - -@end - - -QT7MovieViewOutput::QT7MovieViewOutput(QObject *parent) - :QT7VideoWindowControl(parent), - m_movie(0), - m_movieView(0), - m_layouted(false), - m_winId(0), - m_fullscreen(false), - m_aspectRatioMode(Qt::KeepAspectRatio), - m_brightness(0), - m_contrast(0), - m_hue(0), - m_saturation(0) -{ -} - -QT7MovieViewOutput::~QT7MovieViewOutput() -{ - [(QTMovieView*)m_movieView release]; - [(QTMovie*)m_movie release]; -} - -void QT7MovieViewOutput::setupVideoOutput() -{ - AutoReleasePool pool; - -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieViewOutput::setupVideoOutput" << m_movie << m_winId; -#endif - if (m_movie == 0 || m_winId <= 0) - return; - - NSSize size = [[(QTMovie*)m_movie attributeForKey:@"QTMovieNaturalSizeAttribute"] sizeValue]; - m_nativeSize = QSize(size.width, size.height); - - if (!m_movieView) - m_movieView = [[TransparentQTMovieView alloc] init]; - - [(QTMovieView*)m_movieView setControllerVisible:NO]; - [(QTMovieView*)m_movieView setMovie:(QTMovie*)m_movie]; - - [(NSView *)m_winId addSubview:(QTMovieView*)m_movieView]; - m_layouted = true; - - setDisplayRect(m_displayRect); -} - -void QT7MovieViewOutput::setMovie(void *movie) -{ - if (m_movie != movie) { - if (m_movie) { - if (m_movieView) - [(QTMovieView*)m_movieView setMovie:nil]; - - [(QTMovie*)m_movie release]; - } - - m_movie = movie; - - if (m_movie) - [(QTMovie*)m_movie retain]; - - setupVideoOutput(); - } -} - -void QT7MovieViewOutput::updateNaturalSize(const QSize &newSize) -{ - if (m_nativeSize != newSize) { - m_nativeSize = newSize; - Q_EMIT nativeSizeChanged(); - } -} - -WId QT7MovieViewOutput::winId() const -{ - return m_winId; -} - -void QT7MovieViewOutput::setWinId(WId id) -{ - if (m_winId != id) { - if (m_movieView && m_layouted) { - [(QTMovieView*)m_movieView removeFromSuperview]; - m_layouted = false; - } - - m_winId = id; - setupVideoOutput(); - } -} - -QRect QT7MovieViewOutput::displayRect() const -{ - return m_displayRect; -} - -void QT7MovieViewOutput::setDisplayRect(const QRect &rect) -{ - m_displayRect = rect; - - if (m_movieView) { - AutoReleasePool pool; - [(QTMovieView*)m_movieView setPreservesAspectRatio:(m_aspectRatioMode == Qt::KeepAspectRatio ? YES : NO)]; - [(QTMovieView*)m_movieView setFrame:NSMakeRect(m_displayRect.x(), - m_displayRect.y(), - m_displayRect.width(), - m_displayRect.height())]; - } - -} - -bool QT7MovieViewOutput::isFullScreen() const -{ - return m_fullscreen; -} - -void QT7MovieViewOutput::setFullScreen(bool fullScreen) -{ - m_fullscreen = fullScreen; - setDisplayRect(m_displayRect); -} - -void QT7MovieViewOutput::repaint() -{ -} - -QSize QT7MovieViewOutput::nativeSize() const -{ - return m_nativeSize; -} - -Qt::AspectRatioMode QT7MovieViewOutput::aspectRatioMode() const -{ - return m_aspectRatioMode; -} - -void QT7MovieViewOutput::setAspectRatioMode(Qt::AspectRatioMode mode) -{ - m_aspectRatioMode = mode; - setDisplayRect(m_displayRect); -} - -int QT7MovieViewOutput::brightness() const -{ - return m_brightness; -} - -void QT7MovieViewOutput::setBrightness(int brightness) -{ - m_brightness = brightness; -} - -int QT7MovieViewOutput::contrast() const -{ - return m_contrast; -} - -void QT7MovieViewOutput::setContrast(int contrast) -{ - m_contrast = contrast; - [(TransparentQTMovieView*)m_movieView setContrast:(contrast/100.0+1.0)]; -} - -int QT7MovieViewOutput::hue() const -{ - return m_hue; -} - -void QT7MovieViewOutput::setHue(int hue) -{ - m_hue = hue; -} - -int QT7MovieViewOutput::saturation() const -{ - return m_saturation; -} - -void QT7MovieViewOutput::setSaturation(int saturation) -{ - m_saturation = saturation; -} - diff --git a/src/plugins/qt7/qt7movieviewrenderer.h b/src/plugins/qt7/qt7movieviewrenderer.h deleted file mode 100644 index 2416fe7b..00000000 --- a/src/plugins/qt7/qt7movieviewrenderer.h +++ /dev/null @@ -1,96 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7MOVIEVIEWRENDERER_H -#define QT7MOVIEVIEWRENDERER_H - -#include -#include - -#include -#include - -#include "qt7videooutput.h" -#include - -#include - -QT_BEGIN_NAMESPACE - -class QVideoFrame; - -class QT7PlayerSession; -class QT7PlayerService; -class QGLWidget; -class QGLFramebufferObject; -class QWindow; -class QOpenGLContext; - -class QT7MovieViewRenderer : public QT7VideoRendererControl -{ -public: - QT7MovieViewRenderer(QObject *parent = 0); - ~QT7MovieViewRenderer(); - - void setMovie(void *movie); - void updateNaturalSize(const QSize &newSize); - - QAbstractVideoSurface *surface() const; - void setSurface(QAbstractVideoSurface *surface); - - void renderFrame(const QVideoFrame &); - -protected: - bool event(QEvent *event); - -private: - void setupVideoOutput(); - QVideoFrame convertCIImageToGLTexture(const QVideoFrame &frame); - - void *m_movie; - void *m_movieView; - QSize m_nativeSize; - QAbstractVideoSurface *m_surface; - QVideoFrame m_currentFrame; - QWindow *m_window; - QOpenGLContext *m_context; - QGLFramebufferObject *m_fbo; - CIContext *m_ciContext; - - bool m_pendingRenderEvent; - QMutex m_mutex; -}; - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7movieviewrenderer.mm b/src/plugins/qt7/qt7movieviewrenderer.mm deleted file mode 100644 index a5903157..00000000 --- a/src/plugins/qt7/qt7movieviewrenderer.mm +++ /dev/null @@ -1,509 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#import - -#include "qt7backend.h" - -#include "qt7playercontrol.h" -#include "qt7movieviewrenderer.h" -#include "qt7playersession.h" -#include "qt7ciimagevideobuffer.h" -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -QT_USE_NAMESPACE - -class NSBitmapVideoBuffer : public QAbstractVideoBuffer -{ -public: - NSBitmapVideoBuffer(NSBitmapImageRep *buffer) - : QAbstractVideoBuffer(NoHandle) - , m_buffer(buffer) - , m_mode(NotMapped) - { - [m_buffer retain]; - } - - virtual ~NSBitmapVideoBuffer() - { - [m_buffer release]; - } - - MapMode mapMode() const { return m_mode; } - - uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) - { - if (mode != NotMapped && m_mode == NotMapped) { - if (numBytes) - *numBytes = [m_buffer bytesPerPlane]; - - if (bytesPerLine) - *bytesPerLine = [m_buffer bytesPerRow]; - - m_mode = mode; - - return [m_buffer bitmapData]; - } else { - return 0; - } - } - - void unmap() { m_mode = NotMapped; } - -private: - NSBitmapImageRep *m_buffer; - MapMode m_mode; -}; - -class TextureVideoBuffer : public QAbstractVideoBuffer -{ -public: - TextureVideoBuffer(GLuint textureId) - : QAbstractVideoBuffer(GLTextureHandle) - , m_textureId(textureId) - {} - - virtual ~TextureVideoBuffer() {} - - MapMode mapMode() const { return NotMapped; } - uchar *map(MapMode, int*, int*) { return 0; } - void unmap() {} - - QVariant handle() const - { - return QVariant::fromValue(m_textureId); - } - -private: - GLuint m_textureId; -}; - - -#define VIDEO_TRANSPARENT(m) -(void)m:(NSEvent *)e{[[self superview] m:e];} - -@interface HiddenQTMovieView : QTMovieView -{ -@private - QWindow *m_window; - QT7MovieViewRenderer *m_renderer; - QReadWriteLock m_rendererLock; -} - -- (HiddenQTMovieView *) initWithRenderer:(QT7MovieViewRenderer *)renderer; -- (void) setRenderer:(QT7MovieViewRenderer *)renderer; -- (void) setDrawRect:(const QRect &)rect; -- (CIImage *) view:(QTMovieView *)view willDisplayImage:(CIImage *)img; -@end - -@implementation HiddenQTMovieView - -- (HiddenQTMovieView *) initWithRenderer:(QT7MovieViewRenderer *)renderer -{ - self = [super initWithFrame:NSZeroRect]; - if (self) { - [self setControllerVisible:NO]; - [self setDelegate:self]; - - QWriteLocker lock(&self->m_rendererLock); - self->m_renderer = renderer; - - self->m_window = new QWindow; - self->m_window->setOpacity(0.0); - self->m_window->setGeometry(0,0,1,1); - self->m_window->create(); - - [(NSView *)(self->m_window->winId()) addSubview:self]; - [self setDrawRect:QRect(0,0,1,1)]; - } - return self; -} - -- (void) dealloc -{ - self->m_window->deleteLater(); - [super dealloc]; -} - -- (void) setRenderer:(QT7MovieViewRenderer *)renderer -{ - QWriteLocker lock(&m_rendererLock); - m_renderer = renderer; -} - -- (void) setDrawRect:(const QRect &)rect -{ - NSRect nsrect; - nsrect.origin.x = rect.x(); - nsrect.origin.y = rect.y(); - nsrect.size.width = rect.width(); - nsrect.size.height = rect.height(); - [self setFrame:nsrect]; -} - -- (CIImage *) view:(QTMovieView *)view willDisplayImage:(CIImage *)img -{ - // This method is called from QTMovieView just - // before the image will be drawn. - Q_UNUSED(view); - QReadLocker lock(&m_rendererLock); - AutoReleasePool pool; - - if (m_renderer) { - CGRect bounds = [img extent]; - int w = bounds.size.width; - int h = bounds.size.height; - - QVideoFrame frame; - - QAbstractVideoSurface *surface = m_renderer->surface(); - if (!surface || !surface->isActive()) - return img; - - if (surface->surfaceFormat().handleType() == QAbstractVideoBuffer::CoreImageHandle || - surface->surfaceFormat().handleType() == QAbstractVideoBuffer::GLTextureHandle) { - //surface supports rendering of opengl based CIImage - frame = QVideoFrame(new QT7CIImageVideoBuffer(img), QSize(w,h), QVideoFrame::Format_RGB32 ); - } else { - //Swap R and B colors - CIFilter *colorSwapFilter = [CIFilter filterWithName: @"CIColorMatrix" keysAndValues: - @"inputImage", img, - @"inputRVector", [CIVector vectorWithX: 0 Y: 0 Z: 1 W: 0], - @"inputGVector", [CIVector vectorWithX: 0 Y: 1 Z: 0 W: 0], - @"inputBVector", [CIVector vectorWithX: 1 Y: 0 Z: 0 W: 0], - @"inputAVector", [CIVector vectorWithX: 0 Y: 0 Z: 0 W: 1], - @"inputBiasVector", [CIVector vectorWithX: 0 Y: 0 Z: 0 W: 0], - nil]; - CIImage *img = [colorSwapFilter valueForKey: @"outputImage"]; - NSBitmapImageRep *bitmap =[[NSBitmapImageRep alloc] initWithCIImage:img]; - //requesting the bitmap data is slow, - //but it's better to do it here to avoid blocking the main thread for a long. - [bitmap bitmapData]; - frame = QVideoFrame(new NSBitmapVideoBuffer(bitmap), QSize(w,h), QVideoFrame::Format_RGB32 ); - [bitmap release]; - } - - m_renderer->renderFrame(frame); - } - - return img; -} - -// Override this method so that the movie doesn't stop if -// the window becomes invisible -- (void)viewWillMoveToWindow:(NSWindow *)newWindow -{ - Q_UNUSED(newWindow); -} - - -VIDEO_TRANSPARENT(mouseDown); -VIDEO_TRANSPARENT(mouseDragged); -VIDEO_TRANSPARENT(mouseUp); -VIDEO_TRANSPARENT(mouseMoved); -VIDEO_TRANSPARENT(mouseEntered); -VIDEO_TRANSPARENT(mouseExited); -VIDEO_TRANSPARENT(rightMouseDown); -VIDEO_TRANSPARENT(rightMouseDragged); -VIDEO_TRANSPARENT(rightMouseUp); -VIDEO_TRANSPARENT(otherMouseDown); -VIDEO_TRANSPARENT(otherMouseDragged); -VIDEO_TRANSPARENT(otherMouseUp); -VIDEO_TRANSPARENT(keyDown); -VIDEO_TRANSPARENT(keyUp); -VIDEO_TRANSPARENT(scrollWheel) - -@end - - -QT7MovieViewRenderer::QT7MovieViewRenderer(QObject *parent) - :QT7VideoRendererControl(parent), - m_movie(0), - m_movieView(0), - m_surface(0), - m_window(0), - m_context(0), - m_fbo(0), - m_ciContext(0), - m_pendingRenderEvent(false) -{ -} - -QT7MovieViewRenderer::~QT7MovieViewRenderer() -{ - [(HiddenQTMovieView*)m_movieView setRenderer:0]; - - QMutexLocker locker(&m_mutex); - m_currentFrame = QVideoFrame(); - [(HiddenQTMovieView*)m_movieView release]; - [m_ciContext release]; - delete m_fbo; - delete m_window; -} - -void QT7MovieViewRenderer::setupVideoOutput() -{ - AutoReleasePool pool; - -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7MovieViewRenderer::setupVideoOutput" << m_movie << m_surface; -#endif - - HiddenQTMovieView *movieView = (HiddenQTMovieView*)m_movieView; - - if (movieView && !m_movie) { - [movieView setMovie:nil]; - } - - if (m_movie) { - NSSize size = [[(QTMovie*)m_movie attributeForKey:@"QTMovieNaturalSizeAttribute"] sizeValue]; - - m_nativeSize = QSize(size.width, size.height); - - if (!movieView) { - movieView = [[HiddenQTMovieView alloc] initWithRenderer:this]; - m_movieView = movieView; - [movieView setControllerVisible:NO]; - } - - [movieView setMovie:(QTMovie*)m_movie]; - [movieView setDrawRect:QRect(QPoint(0,0), m_nativeSize)]; - } else { - m_nativeSize = QSize(); - } - - if (m_surface && !m_nativeSize.isEmpty()) { - bool coreImageFrameSupported = !m_surface->supportedPixelFormats(QAbstractVideoBuffer::CoreImageHandle).isEmpty(); - bool glTextureSupported = !m_surface->supportedPixelFormats(QAbstractVideoBuffer::GLTextureHandle).isEmpty(); - - QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle; - QVideoFrame::PixelFormat pixelFormat = QVideoFrame::Format_RGB32; - - if (coreImageFrameSupported) { - handleType = QAbstractVideoBuffer::CoreImageHandle; - } else if (glTextureSupported) { - handleType = QAbstractVideoBuffer::GLTextureHandle; - pixelFormat = QVideoFrame::Format_BGR32; - } - - QVideoSurfaceFormat format(m_nativeSize, pixelFormat, handleType); - - if (m_surface->isActive() && m_surface->surfaceFormat() != format) { -#ifdef QT_DEBUG_QT7 - qDebug() << "Surface format was changed, stop the surface."; -#endif - m_surface->stop(); - } - - if (!m_surface->isActive()) { -#ifdef QT_DEBUG_QT7 - qDebug() << "Starting the surface with format" << format; -#endif - if (!m_surface->start(format)) - qWarning() << "failed to start video surface" << m_surface->error(); - } - } -} - -/*! - Render the CIImage based video frame to FBO and return the video frame with resulting texture -*/ -QVideoFrame QT7MovieViewRenderer::convertCIImageToGLTexture(const QVideoFrame &frame) -{ - if (frame.handleType() != QAbstractVideoBuffer::CoreImageHandle) - return QVideoFrame(); - - if (!m_window) { - QOpenGLContext *qGlContext = 0; - - if (m_surface) - qGlContext = qobject_cast(m_surface->property("GLContext").value()); - - if (qGlContext) { - m_window = new QWindow(); - - QSurfaceFormat format(qGlContext->format()); - - m_context = new QOpenGLContext(m_window); - m_context->setShareContext(qGlContext); - m_context->setFormat(format); - m_context->create(); - - m_window->setFormat(format); - m_window->setGeometry(0, 0, 1, 1); - m_window->setSurfaceType(QWindow::OpenGLSurface); - m_window->create(); - } else { - return QVideoFrame(); - } - } - - if (!m_context) - return QVideoFrame(); - - m_context->makeCurrent(m_window); - - if (!m_fbo || m_fbo->size() != frame.size()) { - delete m_fbo; - m_fbo = new QGLFramebufferObject(frame.size()); - } - - CIImage *ciImg = (CIImage*)(frame.handle().value()); - if (ciImg) { - AutoReleasePool pool; - - QPainter p(m_fbo); - p.beginNativePainting(); - CGLContextObj cglContext = CGLGetCurrentContext(); - if (cglContext) { - if (!m_ciContext) { - NSOpenGLPixelFormat *nsglPixelFormat = [NSOpenGLView defaultPixelFormat]; - CGLPixelFormatObj cglPixelFormat = static_cast([nsglPixelFormat CGLPixelFormatObj]); - - m_ciContext = [CIContext contextWithCGLContext:cglContext - pixelFormat:cglPixelFormat - colorSpace:nil - options:nil]; - - [m_ciContext retain]; - } - - QRect viewport = QRect(0, 0, frame.width(), frame.height()); - CGRect sRect = CGRectMake(viewport.x(), viewport.y(), viewport.width(), viewport.height()); - CGRect dRect = CGRectMake(viewport.x(), viewport.y(), viewport.width(), viewport.height()); - - [m_ciContext drawImage:ciImg inRect:dRect fromRect:sRect]; - } - - p.endNativePainting(); - - QAbstractVideoBuffer *buffer = new TextureVideoBuffer(m_fbo->texture()); - return QVideoFrame(buffer, frame.size(), QVideoFrame::Format_BGR32); - } - - return QVideoFrame(); -} - -void QT7MovieViewRenderer::setMovie(void *movie) -{ - if (movie == m_movie) - return; - - QMutexLocker locker(&m_mutex); - m_movie = movie; - setupVideoOutput(); -} - -void QT7MovieViewRenderer::updateNaturalSize(const QSize &newSize) -{ - if (m_nativeSize != newSize) { - m_nativeSize = newSize; - setupVideoOutput(); - } -} - -QAbstractVideoSurface *QT7MovieViewRenderer::surface() const -{ - return m_surface; -} - -void QT7MovieViewRenderer::setSurface(QAbstractVideoSurface *surface) -{ - if (surface == m_surface) - return; - - QMutexLocker locker(&m_mutex); - - if (m_surface && m_surface->isActive()) - m_surface->stop(); - - m_surface = surface; - setupVideoOutput(); -} - -void QT7MovieViewRenderer::renderFrame(const QVideoFrame &frame) -{ - QMutexLocker locker(&m_mutex); - m_currentFrame = frame; - - if (!m_pendingRenderEvent) - qApp->postEvent(this, new QEvent(QEvent::User), Qt::HighEventPriority); - - m_pendingRenderEvent = true; -} - -bool QT7MovieViewRenderer::event(QEvent *event) -{ - if (event->type() == QEvent::User) { - QMutexLocker locker(&m_mutex); - m_pendingRenderEvent = false; - - if (m_surface->isActive()) { - //For GL texture frames, render in the main thread CIImage based buffers - //to FBO shared with video surface shared context - if (m_surface->surfaceFormat().handleType() == QAbstractVideoBuffer::GLTextureHandle) { - m_currentFrame = convertCIImageToGLTexture(m_currentFrame); - if (m_currentFrame.isValid()) - m_surface->present(m_currentFrame); - } else { - m_surface->present(m_currentFrame); - } - } - - m_currentFrame = QVideoFrame(); - } - - return QT7VideoRendererControl::event(event); -} diff --git a/src/plugins/qt7/qt7serviceplugin.h b/src/plugins/qt7/qt7serviceplugin.h deleted file mode 100644 index 2e04297f..00000000 --- a/src/plugins/qt7/qt7serviceplugin.h +++ /dev/null @@ -1,69 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#ifndef QT7SERVICEPLUGIN_H -#define QT7SERVICEPLUGIN_H - -#include - -QT_BEGIN_NAMESPACE - -class QT7ServicePlugin - : public QMediaServiceProviderPlugin - , public QMediaServiceSupportedFormatsInterface - , public QMediaServiceFeaturesInterface -{ - Q_OBJECT - Q_INTERFACES(QMediaServiceFeaturesInterface) - Q_PLUGIN_METADATA(IID "org.qt-project.qt.mediaserviceproviderfactory/5.0" FILE "qt7.json") - -public: - QT7ServicePlugin(); - - QMediaService* create(QString const& key); - void release(QMediaService *service); - - QMediaServiceProviderHint::Features supportedFeatures(const QByteArray &service) const; - QMultimedia::SupportEstimate hasSupport(const QString &mimeType, const QStringList& codecs) const; - QStringList supportedMimeTypes() const; - -private: - void buildSupportedTypes(); - - QStringList m_supportedMimeTypes; -}; - -QT_END_NAMESPACE - -#endif // QGSTREAMERSERVICEPLUGIN_H diff --git a/src/plugins/qt7/qt7serviceplugin.mm b/src/plugins/qt7/qt7serviceplugin.mm deleted file mode 100644 index 731f278a..00000000 --- a/src/plugins/qt7/qt7serviceplugin.mm +++ /dev/null @@ -1,118 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#import -#import - -#include -#include - -#include "qt7backend.h" -#include "qt7serviceplugin.h" -#include "qt7playerservice.h" - -#include - -QT_BEGIN_NAMESPACE - - -QT7ServicePlugin::QT7ServicePlugin() -{ - buildSupportedTypes(); -} - -QMediaService* QT7ServicePlugin::create(QString const& key) -{ -#ifdef QT_DEBUG_QT7 - qDebug() << "QT7ServicePlugin::create" << key; -#endif -#ifdef QMEDIA_QT7_PLAYER - if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) - return new QT7PlayerService; -#endif - qWarning() << "unsupported key:" << key; - - return 0; -} - -void QT7ServicePlugin::release(QMediaService *service) -{ - delete service; -} - -QMediaServiceProviderHint::Features QT7ServicePlugin::supportedFeatures( - const QByteArray &service) const -{ - if (service == Q_MEDIASERVICE_MEDIAPLAYER) - return QMediaServiceProviderHint::VideoSurface; - else - return QMediaServiceProviderHint::Features(); -} - -QMultimedia::SupportEstimate QT7ServicePlugin::hasSupport(const QString &mimeType, const QStringList& codecs) const -{ - Q_UNUSED(codecs); - - if (m_supportedMimeTypes.contains(mimeType)) - return QMultimedia::ProbablySupported; - - return QMultimedia::MaybeSupported; -} - -QStringList QT7ServicePlugin::supportedMimeTypes() const -{ - return m_supportedMimeTypes; -} - -void QT7ServicePlugin::buildSupportedTypes() -{ - AutoReleasePool pool; - NSArray *utis = [QTMovie movieTypesWithOptions:QTIncludeCommonTypes]; - for (NSString *uti in utis) { - NSString* mimeType = (NSString*)UTTypeCopyPreferredTagWithClass((CFStringRef)uti, kUTTagClassMIMEType); - if (mimeType != 0) { - m_supportedMimeTypes.append(QString::fromUtf8([mimeType UTF8String])); - [mimeType release]; - } - } -} - -QT_END_NAMESPACE diff --git a/src/plugins/qt7/qt7videooutput.h b/src/plugins/qt7/qt7videooutput.h deleted file mode 100644 index 86fc273d..00000000 --- a/src/plugins/qt7/qt7videooutput.h +++ /dev/null @@ -1,109 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QT7VIDEOOUTPUTCONTROL_H -#define QT7VIDEOOUTPUTCONTROL_H - -#include -#include - -#include -#ifndef QT_NO_WIDGETS -#include -#endif -#include -#include - -QT_BEGIN_NAMESPACE - -class QMediaPlaylist; -class QMediaPlaylistNavigator; -class QT7PlayerSession; -class QT7PlayerService; - - -class QT7VideoOutput { -public: - virtual ~QT7VideoOutput() {} - virtual void setMovie(void *movie) = 0; - virtual void updateNaturalSize(const QSize &newSize) = 0; -}; - -#define QT7VideoOutput_iid \ - "org.qt-project.qt.QT7VideoOutput/5.0" -Q_DECLARE_INTERFACE(QT7VideoOutput, QT7VideoOutput_iid) - -class QT7VideoWindowControl : public QVideoWindowControl, public QT7VideoOutput -{ -Q_OBJECT -Q_INTERFACES(QT7VideoOutput) -public: - virtual ~QT7VideoWindowControl() {} - -protected: - QT7VideoWindowControl(QObject *parent) - :QVideoWindowControl(parent) - {} -}; - -class QT7VideoRendererControl : public QVideoRendererControl, public QT7VideoOutput -{ -Q_OBJECT -Q_INTERFACES(QT7VideoOutput) -public: - virtual ~QT7VideoRendererControl() {} - -protected: - QT7VideoRendererControl(QObject *parent) - :QVideoRendererControl(parent) - {} -}; - -#ifndef QT_NO_WIDGETS -class QT7VideoWidgetControl : public QVideoWidgetControl, public QT7VideoOutput -{ -Q_OBJECT -Q_INTERFACES(QT7VideoOutput) -public: - virtual ~QT7VideoWidgetControl() {} - -protected: - QT7VideoWidgetControl(QObject *parent) - :QVideoWidgetControl(parent) - {} -}; -#endif - -QT_END_NAMESPACE - -#endif diff --git a/src/plugins/qt7/qt7videooutput.mm b/src/plugins/qt7/qt7videooutput.mm deleted file mode 100644 index 53486c4e..00000000 --- a/src/plugins/qt7/qt7videooutput.mm +++ /dev/null @@ -1,91 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qt7playercontrol.h" -#include "qt7playersession.h" -#include - -QT_USE_NAMESPACE - -/* -QT7VideoOutputControl::QT7VideoOutputControl(QObject *parent) - :QVideoOutputControl(parent), - m_session(0), - m_output(QVideoOutputControl::NoOutput) -{ -} - -QT7VideoOutputControl::~QT7VideoOutputControl() -{ -} - -void QT7VideoOutputControl::setSession(QT7PlayerSession *session) -{ - m_session = session; -} - -QList QT7VideoOutputControl::availableOutputs() const -{ - return m_outputs; -} - -void QT7VideoOutputControl::enableOutput(QVideoOutputControl::Output output) -{ - if (!m_outputs.contains(output)) - m_outputs.append(output); -} - -QVideoOutputControl::Output QT7VideoOutputControl::output() const -{ - return m_output; -} - -void QT7VideoOutputControl::setOutput(Output output) -{ - if (m_output != output) { - m_output = output; - Q_EMIT videoOutputChanged(m_output); - } -} - -#include "moc_qt7videooutputcontrol.cpp" - -*/ From 5c3a5cf8106e1b873924b296c792448c33ee4df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomi=20Korpip=C3=A4=C3=A4?= Date: Thu, 25 Sep 2014 12:47:01 +0300 Subject: [PATCH 39/48] QMLVideo Example visual update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-36287 Change-Id: I797a995c2ccd6f6fec40fbf50f93e297ae15a9b1 Reviewed-by: Topi Reiniö --- .../video/qmlvideo/images/close.png | Bin 1799 -> 0 bytes .../video/qmlvideo/images/folder.png | Bin 1841 -> 1829 bytes .../video/qmlvideo/images/progress_handle.svg | 35 ---- .../images/progress_handle_pressed.svg | 35 ---- .../video/qmlvideo/images/titlebar.png | Bin 1436 -> 0 bytes .../video/qmlvideo/images/titlebar.sci | 5 - .../multimedia/video/qmlvideo/images/up.png | Bin 662 -> 1268 bytes examples/multimedia/video/qmlvideo/main.cpp | 7 +- .../video/qmlvideo/qml/qmlvideo/Button.qml | 21 ++- .../qmlvideo/qml/qmlvideo/ErrorDialog.qml | 8 +- .../qmlvideo/qml/qmlvideo/FileBrowser.qml | 174 ++++++++++-------- .../video/qmlvideo/qml/qmlvideo/Scene.qml | 11 +- .../qmlvideo/qml/qmlvideo/SceneBasic.qml | 3 +- .../qmlvideo/qml/qmlvideo/SceneFullScreen.qml | 3 +- .../qml/qmlvideo/SceneFullScreenInverted.qml | 3 +- .../qmlvideo/qml/qmlvideo/SceneMulti.qml | 3 +- .../qmlvideo/qml/qmlvideo/SceneOverlay.qml | 2 +- .../qmlvideo/qml/qmlvideo/SceneRotate.qml | 6 +- .../qml/qmlvideo/SceneSelectionPanel.qml | 107 +++++++---- .../qmlvideo/qml/qmlvideo/SeekControl.qml | 30 +-- .../qmlvideo/qml/qmlvideo/VideoFillMode.qml | 6 +- .../qmlvideo/qml/qmlvideo/VideoMetadata.qml | 26 +-- .../qml/qmlvideo/VideoPlaybackRate.qml | 10 +- .../video/qmlvideo/qml/qmlvideo/VideoSeek.qml | 6 +- .../video/qmlvideo/qml/qmlvideo/main.qml | 62 +++++-- .../multimedia/video/qmlvideo/qmlvideo.qrc | 5 - .../qml/qmlvideofx/ContentCamera.qml | 43 ----- .../video/qmlvideofx/qmlvideofx.qrc | 1 - 28 files changed, 290 insertions(+), 322 deletions(-) delete mode 100644 examples/multimedia/video/qmlvideo/images/close.png delete mode 100644 examples/multimedia/video/qmlvideo/images/progress_handle.svg delete mode 100644 examples/multimedia/video/qmlvideo/images/progress_handle_pressed.svg delete mode 100644 examples/multimedia/video/qmlvideo/images/titlebar.png delete mode 100644 examples/multimedia/video/qmlvideo/images/titlebar.sci delete mode 100644 examples/multimedia/video/qmlvideofx/qml/qmlvideofx/ContentCamera.qml diff --git a/examples/multimedia/video/qmlvideo/images/close.png b/examples/multimedia/video/qmlvideo/images/close.png deleted file mode 100644 index 6904df0e447a1b85b232e8950c351b7be648505d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1799 zcmV+i2l)7jP)KhouR`2`?SgHD-RcU! z4A^eiU5cgb*hE+SMalTsgP-%${h3)xj>qzJRqjePX0wGy=Q<+wrnEnI=LZt~?~wx!?8f z{EjUjc7>ddu*cE(CTwRafHJi7!mn}iin!QQ;?*OE?|Il;PH64JVNoi8s=-7hXK+&V zkcjUT=PdL!*mZ|xOe%od5~2~6T5QT227KbyWPWu_vEM}hWh7aLpZzu^J9VAt2WEgt z0Qf}{&y|9dxq*PiUxNLkHQ+l=DER}N)P9&r@v?9VtrRe_F&UuR2B3o6t_@~byevGB zwJ2KNwFNk)P;<#K-8~edz~sqD3+)s?gKi&>Qh=L&yjaiBR6jlaxJvywmXx9isL{$ZV~xcH+Zf$}KCS>^XCi0y@u~qqeD2 z(cCQ_0OjLNnK{$-ecO^H z6xHroEj%E#d}ssk0ssz^JH_b$xl5N)c*_=#xELVrr-5Mcy&yiQiRaR~Y#Ft0-D(7o zT2`Kp%lU0DOJqNE&eB1m&0@5KfHA_jHHIpN45Yz zlY2g50+0|L@I)~@VS;D}1ee?t;v3NNIcWJ*gJcc4Luic+!1Dn3iQFNSZWK;Jj2=yc zjvuF>0m4*b5PxIF49cmlPJ3?^ARz!Q*Z}-U?hx{uMsU3tJTVxa@PlCS*U|Dpw0ywl zz7jw-=2}~Ta&m`|%{++fMbRgoLZ3(l!Q%fw%!d&3e&Um2G!Lw^0eB7oKae|wY)b;! zrvKr?ls{#P@p|*AQ*>wcY|646u_b`ivYwJJ`ThX=$op#NR{{`Uh*a6jkSf)3A1U@0 zQf%A;f|cG=w0vv>FaZE%J5MqE!HUN9c{X&xtfW;3&p6_i) zmF2F8hW5E4LY`NS4rk0w6VM43m}Cm}v!5Z?yzRme76#vnW886a8! z@n@R369dQ)VjZ8_0E`E~ZcV%ifW;3+*hCCr!{UEOo{v;lJ3=u|jKw%%gg`i5H_V;5 zT+}@9870?<`~h}p;&p(mygV9$md{Ti{x`IIKJvVr#q!H}GscMtDLl~(h->VLwl)ud z+KD&W0E`m=(gU3afWmd_sA%n4kC0Y~KZ`tHfIP2H!ILX(7$+uToCq5rZmwTXw{YK7 z00{wb#RlM8%>$iUrJ)B7P{G`}9`R?;@_o?q##c~sUeKH1iBfn%hiE)_km~2nGXh90 zpW6cLBzO4e3-oOVShQ6O+*=RESF}S5niaO|H6^$sIyp z+5n6Nzz%YUkPgrk4AQ_28)yhRUvqsuonOA3`qtI?idP}-ju=5Dn>SPb(4lk<$>6Wm ztEo>cmJ%Q#0Iu2qY$taJ#S9{N(TP`&dDWV?ZfwP?5WEM#%jgBL#|(8OfYkDpExe5EHT6k*n pbcP|Mc;2Aw_P-iD66KKx_!kJI7Ht%E#-RWJ002ovPDHLkV1kHfP1XPa diff --git a/examples/multimedia/video/qmlvideo/images/folder.png b/examples/multimedia/video/qmlvideo/images/folder.png index e53e2ad464eb993256a6c3567fc940498e26fc8b..62d97004fb01e085c3060619ac68dbec2fa7e8aa 100644 GIT binary patch literal 1829 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 diff --git a/examples/multimedia/video/qmlvideo/images/progress_handle.svg b/examples/multimedia/video/qmlvideo/images/progress_handle.svg deleted file mode 100644 index 7ad9014e..00000000 --- a/examples/multimedia/video/qmlvideo/images/progress_handle.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/multimedia/video/qmlvideo/images/progress_handle_pressed.svg b/examples/multimedia/video/qmlvideo/images/progress_handle_pressed.svg deleted file mode 100644 index c9c6c486..00000000 --- a/examples/multimedia/video/qmlvideo/images/progress_handle_pressed.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/examples/multimedia/video/qmlvideo/images/titlebar.png b/examples/multimedia/video/qmlvideo/images/titlebar.png deleted file mode 100644 index 51c90082d052a94af34488ca9a13842122f7d7a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1436 zcmV;N1!MY&P)00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXW1 z0xv7!9LBW(000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}000E{NklLiq!w7*fp`1oo9~;MC5Hkm0Ga8f)%S;LHn|^l4>W+QI@MK<0LU%? zTCN^g=1tdNq`Dpph=PESv9&Y_yal)K%ETFKXCtR`vY67D3wB9@+&cbGgIArTN6+wSAX zkGy^RmUr*o{T4#Fkk#tskC!ih`Tfb0Cxj5Vy1F8SK-YDE!#OZB%*L~eNSa)23&d?U zSw1i0>gRr9IT1mtHP62Lmb0@nE-o&3^XAQ;^5Vq{d;a`6A3l8G`uZB@98Hs4A;w4} zZZ8P$T;_Us-Wa3LiY3JoBbsA{322^IDj+WNr!^z_J?9)<*YWV-LmoYP#Ov3u@#p8~ zTwPspd3nidwF01RTNaB2BC@r4CKHDcwm?9#oSA`>USgwAW7e}#gkM0Yvk41^nr1^7 z&N;5HuQ@q6;px+-_|@_O@8ADLBaYYwR2A=iy0?Jz0cyTE=eGiSB!O3eXvr9oHkkl) zJria&VhTXjdXO$lOS6-T2wfXkEEZf|Uh??yV|9$6 z$kf}E2}3pUBO*h1vm}>TF;RnPvP?}ajZsZ(s`c^F`G#o9X1&4r25)A>7;$dUeu#sd zH0nr`HO-^wWQOLC8t2no3_(Fez${xkBmh=yunfEn^?XuQ?3Ij+Zzn(@H!pav#Pqq_?!|nMq-S~Ct{2l-QZ~b zHR{$P`zF6tD!u{4zWIxhQbJa`7!wn!ntVca%bFsRXx#Sb;tV@Ca^rcTGS~=!F~4Rd zD^=}7d=H{l@KpDMcDE-MbeynXOK|hy7X0?B$O;vFv(d|Qf^dRaKe_z@t5t}6@ZhHA%GI8viucfZ4DLUOJ z%g-f2H+QV4licii=|7xc|4$k9K5-Z1_4nOT1!U>l1aAsH8U}fi7AzZCsS=07?@QuLn2Bde0{8v^Kf6`()~Xj@TAnpKdC8`Lf!&sHg;q@=(~U%$M( zT(8_%FTW^V-_X+15@d#vkuFe$ZgFK^Nn(X=Ua>OF1ees}+T7#d8#0MoBXEYLU9GXQxBrqI_HztY@Xxa#7Ppj3o=u^L<)Qdy9y zACy|0Us{w5jJPyqkW~d%&PAz-CHX}m`T04pPz=b(FUc>?$S+WE4mMNJ@J&q4%mWE% zf_3=%T6yLbmn7yTr+T{BDgn*V%gju%aP^Az77Ltu^?{Dj2SqJXRKtXT=?BDwCtM&0p7c}mfa$#mn6UdBzjZM%Fs6CBIEGZ* zS~GROm$RWr`(DGQ1?R4rZ+N65vSvm8gZW?fGn%#QS}+KIuEBnAvb~;U`Nsi%k*|0eOlwSa_!rIo8bZ;=T@q0$a;AATeJNhA+d{% zOSE3}v5GZmo%#~&yYAQulP7Xn8)tAcajww`UlQ}9>qOXm4&xU-LEbN>u`X-YIu-JK z{={qbdzaNIF8#fC_2Ww2OWT*5asFNR_36j+m78n2dwz&%{)tVt&Mn;f_^!F3xaf_= e2hP>ANHDZN+w`o>Y1uwdIppc;=d#Wzp$P!%^~9C{ delta 651 zcmV;60(AZK36=$r7k?561^@s6R?d!B00001b5ch_0Itp)=>Px#24YJ`L;(K){{a7> zy{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iXS@05=qo0T_V*00I_CL_t(I z%e|D(YZE~f$3JgoH`$m3@}o^mwTd1D5%pxX218XWdhp;Mpnpf(i`0|eyeoL`%|F2Z zKoAr>NKwR-pa?2eNi}JHY?GSo?0V>K8`3uF#RrcWn3>P}zM1z1{-ar;k2L*<^-?UZ zZ);mF887GnD}eb|B@?N%y?F8J!OrztlfA>o^i!Y$#Qs*1Xe|XNVcj`5yZ>f+Htp=m zRdqVkJK(lCx_{Q>gKzjgtZW-dYbj)M&cmrnac=rTQu}%tgxf_I11D8#UelwLwtl%SZ3m%V*wrby*e9oJ{~dtOz1tD`>95;{Cw9UE)=v#v|Z0 z&{qSX&%guVnynp@P6k`R3f&HYmVgLNU>jK~3~(JNA$eAihC-5CH`U8*?qet_?YCeyi2~ z`Qmwd%yl;oiwc1P{XQ5&ElO!C_;z!E8^H0fuk;S-F9DB%c61do;_HY3aljr{4D`a+ l+EgVYx?e`D2SX#j0sXq9GS=auNdN!<07*qoLsetContextProperty("videoPath", videoPath); QMetaObject::invokeMethod(rootObject, "init"); + viewer.setMinimumSize(QSize(640, 360)); viewer.show(); return app.exec(); diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Button.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Button.qml index ea686ea3..4f5cbada 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Button.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Button.qml @@ -33,30 +33,31 @@ import QtQuick 2.0 -Rectangle { +Item { id: root - color: textColor - radius: 0.25 * height property string text - property color bgColor: "white" - property color bgColorSelected: "red" - property color textColor: "black" + property color bgColor: "#757575" + property color bgColorSelected: "#bdbdbd" + property color textColor: "white" + property color textColorSelected: "black" property alias enabled: mouseArea.enabled + property alias radius: bgr.radius signal clicked Rectangle { - anchors { fill: parent; margins: 1 } + id: bgr + anchors.fill: parent color: mouseArea.pressed ? bgColorSelected : bgColor - radius: 0.25 * height + radius: height / 15 Text { id: text anchors.centerIn: parent text: root.text - font.pixelSize: 0.5 * parent.height - color: mouseArea.pressed ? bgColor : textColor + font.pixelSize: 0.4 * parent.height + color: mouseArea.pressed ? textColorSelected : textColor horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/ErrorDialog.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/ErrorDialog.qml index c9e7cd10..33a55ebd 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/ErrorDialog.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/ErrorDialog.qml @@ -38,6 +38,8 @@ Rectangle { color: "transparent" opacity: 0.0 property alias enabled: mouseArea.enabled + property int dialogWidth: 300 + property int dialogHeight: 200 state: enabled ? "on" : "baseState" states: [ @@ -70,9 +72,9 @@ Rectangle { Rectangle { anchors.centerIn: parent - width: 300 - height: 200 - radius: 10 + width: dialogWidth + height: dialogHeight + radius: 5 color: "white" Text { diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml index 466ea8b9..33109bd3 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/FileBrowser.qml @@ -40,6 +40,9 @@ Rectangle { property string folder + property int itemHeight: Math.min(parent.width, parent.height) / 15 + property int buttonHeight: Math.min(parent.width, parent.height) / 12 + signal fileSelected(string file) function selectFile(file) { @@ -66,12 +69,12 @@ Rectangle { Rectangle { id: root - color: "white" + color: "black" property bool showFocusHighlight: false property variant folders: folders1 property variant view: view1 property alias folder: folders1.folder - property color textColor: "black" + property color textColor: "white" FolderListModel { id: folders1 @@ -103,34 +106,39 @@ Rectangle { fileBrowser.selectFile(path) } width: root.width - height: 52 + height: folderImage.height color: "transparent" Rectangle { - id: highlight; visible: false + id: highlight + visible: false anchors.fill: parent - color: palette.highlight - gradient: Gradient { - GradientStop { id: t1; position: 0.0; color: palette.highlight } - GradientStop { id: t2; position: 1.0; color: Qt.lighter(palette.highlight) } - } + anchors.leftMargin: 5 + anchors.rightMargin: 5 + color: "#212121" } Item { - width: 48; height: 48 + id: folderImage + width: itemHeight + height: itemHeight Image { + id: folderPicture source: "qrc:/folder.png" - anchors.centerIn: parent + width: itemHeight * 0.9 + height: itemHeight * 0.9 + anchors.left: parent.left + anchors.margins: 5 visible: folders.isFolder(index) } } Text { id: nameText - anchors.fill: parent; verticalAlignment: Text.AlignVCenter + anchors.fill: parent; + verticalAlignment: Text.AlignVCenter text: fileName - anchors.leftMargin: 54 - font.pixelSize: 32 + anchors.leftMargin: itemHeight + 10 color: (wrapper.ListView.isCurrentItem && root.showFocusHighlight) ? palette.highlightedText : textColor elide: Text.ElideRight } @@ -142,7 +150,7 @@ Rectangle { root.showFocusHighlight = false; wrapper.ListView.view.currentIndex = index; } - onClicked: { if (folders == wrapper.ListView.view.model) launch() } + onClicked: { if (folders === wrapper.ListView.view.model) launch() } } states: [ @@ -160,17 +168,12 @@ Rectangle { id: view1 anchors.top: titleBar.bottom anchors.bottom: cancelButton.top - x: 0 width: parent.width model: folders1 delegate: folderDelegate highlight: Rectangle { - color: palette.highlight + color: "#212121" visible: root.showFocusHighlight && view1.count != 0 - gradient: Gradient { - GradientStop { id: t1; position: 0.0; color: palette.highlight } - GradientStop { id: t2; position: 1.0; color: Qt.lighter(palette.highlight) } - } width: view1.currentItem == null ? 0 : view1.currentItem.width } highlightMoveVelocity: 1000 @@ -215,12 +218,8 @@ Rectangle { model: folders2 delegate: folderDelegate highlight: Rectangle { - color: palette.highlight + color: "#212121" visible: root.showFocusHighlight && view2.count != 0 - gradient: Gradient { - GradientStop { id: t1; position: 0.0; color: palette.highlight } - GradientStop { id: t2; position: 1.0; color: Qt.lighter(palette.highlight) } - } width: view1.currentItem == null ? 0 : view1.currentItem.width } highlightMoveVelocity: 1000 @@ -254,19 +253,29 @@ Rectangle { } Rectangle { - id: cancelButton - width: 100 - height: titleBar.height - 7 + width: parent.width + height: buttonHeight + 10 + anchors.bottom: parent.bottom color: "black" - anchors { bottom: parent.bottom; horizontalCenter: parent.horizontalCenter } + } + + Rectangle { + id: cancelButton + width: parent.width + height: buttonHeight + color: "#212121" + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 5 + radius: buttonHeight / 15 Text { - anchors { fill: parent; margins: 4 } + anchors.fill: parent text: "Cancel" color: "white" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - font.pixelSize: 20 } MouseArea { @@ -277,55 +286,66 @@ Rectangle { Keys.onPressed: { root.keyPressed(event.key); - if (event.key == Qt.Key_Return || event.key == Qt.Key_Select || event.key == Qt.Key_Right) { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Select || event.key === Qt.Key_Right) { view.currentItem.launch(); event.accepted = true; - } else if (event.key == Qt.Key_Left) { + } else if (event.key === Qt.Key_Left) { up(); } } - BorderImage { - source: "qrc:/titlebar.sci"; - width: parent.width; - height: 52 - y: -7 + + Rectangle { id: titleBar + width: parent.width + height: buttonHeight + 10 + anchors.top: parent.top + color: "black" Rectangle { - id: upButton - width: 48 - height: titleBar.height - 7 - color: "transparent" - Image { anchors.centerIn: parent; source: "qrc:/up.png" } - MouseArea { id: upRegion; anchors.centerIn: parent - width: 56 - height: 56 - onClicked: up() - } - states: [ - State { - name: "pressed" - when: upRegion.pressed - PropertyChanges { target: upButton; color: palette.highlight } + width: parent.width; + height: buttonHeight + color: "#212121" + anchors.margins: 5 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + radius: buttonHeight / 15 + + Rectangle { + id: upButton + width: buttonHeight + height: buttonHeight + color: "transparent" + Image { + width: itemHeight + height: itemHeight + anchors.centerIn: parent + source: "qrc:/up.png" } - ] - } + MouseArea { id: upRegion; anchors.centerIn: parent + width: buttonHeight + height: buttonHeight + onClicked: up() + } + states: [ + State { + name: "pressed" + when: upRegion.pressed + PropertyChanges { target: upButton; color: palette.highlight } + } + ] + } - Rectangle { - color: "gray" - x: 48 - width: 1 - height: 44 - } - - Text { - anchors.left: upButton.right; anchors.right: parent.right; height: parent.height - anchors.leftMargin: 4; anchors.rightMargin: 4 - text: folders.folder - color: "white" - elide: Text.ElideLeft; horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignVCenter - font.pixelSize: 32 + Text { + anchors.left: upButton.right; anchors.right: parent.right; height: parent.height + anchors.leftMargin: 5; anchors.rightMargin: 5 + text: folders.folder + color: "white" + elide: Text.ElideLeft; + horizontalAlignment: Text.AlignLeft; + verticalAlignment: Text.AlignVCenter + } } } @@ -366,14 +386,14 @@ Rectangle { function keyPressed(key) { switch (key) { - case Qt.Key_Up: - case Qt.Key_Down: - case Qt.Key_Left: - case Qt.Key_Right: - root.showFocusHighlight = true; + case Qt.Key_Up: + case Qt.Key_Down: + case Qt.Key_Left: + case Qt.Key_Right: + root.showFocusHighlight = true; break; - default: - // do nothing + default: + // do nothing break; } } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Scene.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Scene.qml index 5443dc84..04c852a9 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/Scene.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/Scene.qml @@ -39,9 +39,9 @@ Rectangle { property alias buttonHeight: closeButton.height property string source1 property string source2 - property int contentWidth: 250 + property int contentWidth: parent.width / 2 property real volume: 0.25 - property int margins: 10 + property int margins: 5 property QtObject content signal close @@ -54,9 +54,12 @@ Rectangle { right: parent.right margins: root.margins } - width: 50 - height: 30 + width: Math.max(parent.width, parent.height) / 12 + height: Math.min(parent.width, parent.height) / 12 z: 2.0 + bgColor: "#212121" + bgColorSelected: "#757575" + textColorSelected: "white" text: "Back" onClicked: root.close() } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml index cb50e365..dd2dfaf5 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneBasic.qml @@ -59,8 +59,7 @@ Scene { } text: content.started ? "Tap the screen to stop content" : "Tap the screen to start content" - color: "yellow" - font.pixelSize: 20 + color: "#e0e0e0" z: 2.0 } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreen.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreen.qml index aa2b4267..63f94de2 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreen.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreen.qml @@ -86,8 +86,7 @@ Scene { margins: 20 } text: "Tap on the content to toggle full-screen mode" - color: "yellow" - font.pixelSize: 20 + color: "#e0e0e0" z: 2.0 } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreenInverted.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreenInverted.qml index 7824589e..99159591 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreenInverted.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneFullScreenInverted.qml @@ -91,8 +91,7 @@ Scene { margins: 20 } text: "Tap on the content to toggle full-screen mode" - color: "yellow" - font.pixelSize: 20 + color: "#e0e0e0" z: 2.0 } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneMulti.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneMulti.qml index f31cc958..042c609d 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneMulti.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneMulti.qml @@ -65,8 +65,7 @@ Scene { margins: 20 } text: content() ? content().started ? "Tap to stop" : "Tap to start" : "" - color: "yellow" - font.pixelSize: 20 + color: "#e0e0e0" } MouseArea { diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneOverlay.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneOverlay.qml index 1f4559a5..bdeff4e1 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneOverlay.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneOverlay.qml @@ -53,7 +53,7 @@ Scene { y: 0.5 * parent.height width: content.width height: content.height - color: "yellow" + color: "#e0e0e0" opacity: 0.5 SequentialAnimation on x { diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneRotate.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneRotate.qml index cfba508a..2ad65f60 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneRotate.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneRotate.qml @@ -56,7 +56,7 @@ Scene { bottom: rotateNegativeButton.top margins: parent.margins } - width: 90 + width: Math.max(parent.width, parent.height) / 10 height: root.buttonHeight text: "Rotate +" + delta onClicked: content.rotation = content.rotation + delta @@ -69,7 +69,7 @@ Scene { verticalCenter: parent.verticalCenter margins: parent.margins } - width: 90 + width: Math.max(parent.width, parent.height) / 10 height: root.buttonHeight text: "Rotate -" + delta onClicked: content.rotation = content.rotation - delta @@ -82,7 +82,7 @@ Scene { verticalCenter: parent.verticalCenter margins: parent.margins } - width: 30 + width: Math.max(parent.width, parent.height) / 25 height: root.buttonHeight enabled: false text: content.rotation % 360 diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneSelectionPanel.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneSelectionPanel.qml index 976644b4..8e6d11a8 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneSelectionPanel.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SceneSelectionPanel.qml @@ -39,44 +39,64 @@ Rectangle { property string sceneSource: "" ListModel { - id: list - ListElement { name: "multi"; source: "SceneMulti.qml" } - ListElement { name: "video"; source: "VideoBasic.qml" } - ListElement { name: "video-drag"; source: "VideoDrag.qml" } - ListElement { name: "video-fillmode"; source: "VideoFillMode.qml" } - ListElement { name: "video-fullscreen"; source: "VideoFullScreen.qml" } - ListElement { name: "video-fullscreen-inverted"; source: "VideoFullScreenInverted.qml" } - ListElement { name: "video-metadata"; source: "VideoMetadata.qml" } - ListElement { name: "video-move"; source: "VideoMove.qml" } - ListElement { name: "video-overlay"; source: "VideoOverlay.qml" } - ListElement { name: "video-playbackrate"; source: "VideoPlaybackRate.qml" } - ListElement { name: "video-resize"; source: "VideoResize.qml" } - ListElement { name: "video-rotate"; source: "VideoRotate.qml" } - ListElement { name: "video-spin"; source: "VideoSpin.qml" } - ListElement { name: "video-seek"; source: "VideoSeek.qml" } - ListElement { name: "camera"; source: "CameraBasic.qml" } - ListElement { name: "camera-drag"; source: "CameraDrag.qml" } - ListElement { name: "camera-fullscreen"; source: "CameraFullScreen.qml" } - ListElement { name: "camera-fullscreen-inverted"; source: "CameraFullScreenInverted.qml" } - ListElement { name: "camera-move"; source: "CameraMove.qml" } - ListElement { name: "camera-overlay"; source: "CameraOverlay.qml" } - ListElement { name: "camera-resize"; source: "CameraResize.qml" } - ListElement { name: "camera-rotate"; source: "CameraRotate.qml" } - ListElement { name: "camera-spin"; source: "CameraSpin.qml" } + id: videolist + ListElement { name: "Multi"; source: "SceneMulti.qml" } + ListElement { name: "Video"; source: "VideoBasic.qml" } + ListElement { name: "Drag"; source: "VideoDrag.qml" } + ListElement { name: "Fillmode"; source: "VideoFillMode.qml" } + ListElement { name: "Fullscreen"; source: "VideoFullScreen.qml" } + ListElement { name: "Fullscreen-inverted"; source: "VideoFullScreenInverted.qml" } + ListElement { name: "Metadata"; source: "VideoMetadata.qml" } + ListElement { name: "Move"; source: "VideoMove.qml" } + ListElement { name: "Overlay"; source: "VideoOverlay.qml" } + ListElement { name: "Playback Rate"; source: "VideoPlaybackRate.qml" } + ListElement { name: "Resize"; source: "VideoResize.qml" } + ListElement { name: "Rotate"; source: "VideoRotate.qml" } + ListElement { name: "Spin"; source: "VideoSpin.qml" } + ListElement { name: "Seek"; source: "VideoSeek.qml" } + } + + ListModel { + id: cameralist + ListElement { name: "Camera"; source: "CameraBasic.qml" } + ListElement { name: "Drag"; source: "CameraDrag.qml" } + ListElement { name: "Fullscreen"; source: "CameraFullScreen.qml" } + ListElement { name: "Fullscreen-inverted"; source: "CameraFullScreenInverted.qml" } + ListElement { name: "Move"; source: "CameraMove.qml" } + ListElement { name: "Overlay"; source: "CameraOverlay.qml" } + ListElement { name: "Resize"; source: "CameraResize.qml" } + ListElement { name: "Rotate"; source: "CameraRotate.qml" } + ListElement { name: "Spin"; source: "CameraSpin.qml" } } Component { - id: delegate + id: leftDelegate Item { - id: delegateItem - width: root.width - height: itemHeight + width: root.width / 2 + height: 0.8 * itemHeight Button { - id: selectorItem - anchors.centerIn: parent - width: 0.9 * parent.width - height: 0.8 * itemHeight + anchors.fill: parent + anchors.margins: 5 + anchors.rightMargin: 2.5 + anchors.bottomMargin: 0 + text: name + onClicked: root.sceneSource = source + } + } + } + + Component { + id: rightDelegate + Item { + width: root.width / 2 + height: 0.8 * itemHeight + + Button { + anchors.fill: parent + anchors.margins: 5 + anchors.leftMargin: 2.5 + anchors.bottomMargin: 0 text: name onClicked: root.sceneSource = source } @@ -85,20 +105,29 @@ Rectangle { Flickable { anchors.fill: parent - contentHeight: (itemHeight * list.count) + layout.anchors.topMargin + layout.spacing + contentHeight: (itemHeight * videolist.count) + 10 clip: true - Column { + Row { id: layout - anchors { fill: parent - topMargin: 10 + topMargin: 5 + bottomMargin: 5 } - Repeater { - model: list - delegate: delegate + Column { + Repeater { + model: videolist + delegate: leftDelegate + } + } + + Column { + Repeater { + model: cameralist + delegate: rightDelegate + } } } } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SeekControl.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SeekControl.qml index 9c381013..f14fa402 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/SeekControl.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/SeekControl.qml @@ -35,7 +35,7 @@ import QtQuick 2.0 Item { id: seekControl - height: 46 + height: Math.min(parent.width, parent.height) / 20 property int duration: 0 property int playPosition: 0 property int seekPosition: 0 @@ -45,8 +45,9 @@ Item { Rectangle { id: background anchors.fill: parent - color: "black" + color: "white" opacity: 0.3 + radius: parent.height / 15 } Rectangle { @@ -60,7 +61,6 @@ Item { Text { width: 90 anchors { left: parent.left; top: parent.top; bottom: parent.bottom; leftMargin: 10 } - font { family: "Nokia Sans S60"; pixelSize: 24 } horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter color: "white" @@ -71,7 +71,6 @@ Item { Text { width: 90 anchors { right: parent.right; top: parent.top; bottom: parent.bottom; rightMargin: 10 } - font { family: "Nokia Sans S60"; pixelSize: 24 } horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignVCenter color: "white" @@ -79,35 +78,36 @@ Item { text: formatTime(duration) } - Image { + Rectangle { id: progressHandle - height: 46 - width: 10 - source: mouseArea.pressed ? "qrc:/images/progress_handle_pressed.svg" : "qrc:/images/progress_handle.svg" + height: parent.height + width: parent.height / 2 + color: "white" + opacity: 0.5 anchors.verticalCenter: progressBar.verticalCenter - x: seekControl.duration == 0 ? 0 : seekControl.playPosition / seekControl.duration * 630 + x: seekControl.duration == 0 ? 0 : seekControl.playPosition / seekControl.duration * background.width MouseArea { id: mouseArea anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom } - height: 46+16 - width: height + height: parent.height + width: parent.height * 2 enabled: seekControl.enabled drag { target: progressHandle axis: Drag.XAxis minimumX: 0 - maximumX: 631 + maximumX: background.width } onPressed: { seekControl.seeking = true; } onCanceled: { - seekControl.seekPosition = progressHandle.x * seekControl.duration / 630 + seekControl.seekPosition = progressHandle.x * seekControl.duration / background.width seekControl.seeking = false } onReleased: { - seekControl.seekPosition = progressHandle.x * seekControl.duration / 630 + seekControl.seekPosition = progressHandle.x * seekControl.duration / background.width seekControl.seeking = false mouse.accepted = true } @@ -120,7 +120,7 @@ Item { interval: 300 running: seekControl.seeking onTriggered: { - seekControl.seekPosition = progressHandle.x*seekControl.duration/630 + seekControl.seekPosition = progressHandle.x*seekControl.duration / background.width } } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoFillMode.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoFillMode.qml index f623aa41..b114d5bb 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoFillMode.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoFillMode.qml @@ -54,16 +54,16 @@ Scene { verticalCenter: parent.verticalCenter margins: parent.margins } - width: 150 + width: Math.max(parent.width, parent.height) / 5 height: root.buttonHeight text: "PreserveAspectFit" onClicked: { if (!content.dummy) { var video = content.contentItem() - if (video.fillMode == VideoOutput.Stretch) { + if (video.fillMode === VideoOutput.Stretch) { video.fillMode = VideoOutput.PreserveAspectFit text = "PreserveAspectFit" - } else if (video.fillMode == VideoOutput.PreserveAspectFit) { + } else if (video.fillMode === VideoOutput.PreserveAspectFit) { video.fillMode = VideoOutput.PreserveAspectCrop text = "PreserveAspectCrop" } else { diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoMetadata.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoMetadata.qml index 00580f78..05c6dd76 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoMetadata.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoMetadata.qml @@ -56,55 +56,55 @@ Scene { Column { anchors.fill: parent Text { - color: "yellow" + color: "#e0e0e0" text: "Title:" + content.contentItem().metaData.title } Text { - color: "yellow" + color: "#e0e0e0" text: "Size:" + content.contentItem().metaData.size } Text { - color: "yellow" + color: "#e0e0e0" text: "Resolution:" + content.contentItem().metaData.resolution } Text { - color: "yellow" + color: "#e0e0e0" text: "Media type:" + content.contentItem().metaData.mediaType } Text { - color: "yellow" + color: "#e0e0e0" text: "Video codec:" + content.contentItem().metaData.videoCodec } Text { - color: "yellow" + color: "#e0e0e0" text: "Video bit rate:" + content.contentItem().metaData.videoBitRate } Text { - color: "yellow" + color: "#e0e0e0" text: "Video frame rate:" +content.contentItem().metaData.videoFrameRate } Text { - color: "yellow" + color: "#e0e0e0" text: "Audio codec:" + content.contentItem().metaData.audioCodec } Text { - color: "yellow" + color: "#e0e0e0" text: "Audio bit rate:" + content.contentItem().metaData.audioBitRate } Text { - color: "yellow" + color: "#e0e0e0" text: "Date:" + content.contentItem().metaData.date } Text { - color: "yellow" + color: "#e0e0e0" text: "Description:" + content.contentItem().metaData.description } Text { - color: "yellow" + color: "#e0e0e0" text: "Copyright:" + content.contentItem().metaData.copyright } Text { - color: "yellow" + color: "#e0e0e0" text: "Seekable:" + content.contentItem().metaData.seekable } } diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoPlaybackRate.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoPlaybackRate.qml index 5c1e6ab1..45d599b3 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoPlaybackRate.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoPlaybackRate.qml @@ -56,12 +56,12 @@ Scene { bottom: decreaseButton.top margins: parent.margins } - width: 90 + width: Math.max(parent.width, parent.height) / 10 height: root.buttonHeight text: "Increase" onClicked: { var video = content.contentItem() - video.playbackRate = video.playbackRate + delta + video.playbackRate += delta } } @@ -72,12 +72,12 @@ Scene { verticalCenter: parent.verticalCenter margins: parent.margins } - width: 90 + width: Math.max(parent.width, parent.height) / 10 height: root.buttonHeight text: "Decrease" onClicked: { var video = content.contentItem() - video.playbackRate = video.playbackRate - delta + video.playbackRate -= delta } } @@ -88,7 +88,7 @@ Scene { verticalCenter: parent.verticalCenter margins: parent.margins } - width: 50 + width: Math.max(parent.width, parent.height) / 25 height: root.buttonHeight enabled: false text: Math.round(10 * content.contentItem().playbackRate) / 10 diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoSeek.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoSeek.qml index 2d43bdf6..05a312e3 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoSeek.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/VideoSeek.qml @@ -36,6 +36,7 @@ import QtQuick 2.0 Scene { id: root property string contentType: "video" + contentWidth: parent.width Content { id: content @@ -51,13 +52,12 @@ Scene { anchors { left: parent.left right: parent.right - leftMargin: 100 - rightMargin: 140 + margins: 10 bottom: parent.bottom } duration: content.contentItem() ? content.contentItem().duration : 0 playPosition: content.contentItem() ? content.contentItem().position : 0 - onSeekPositionChanged: { content.contentItem().seek(seekPosition); } + onSeekPositionChanged: content.contentItem().seek(seekPosition); } Component.onCompleted: root.content = content diff --git a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml index c2c15439..96acb4bc 100644 --- a/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml +++ b/examples/multimedia/video/qmlvideo/qml/qmlvideo/main.qml @@ -35,22 +35,21 @@ import QtQuick 2.0 Rectangle { id: root - width: 640 - height: 360 + anchors.fill: parent color: "black" property string source1 property string source2 - property color bgColor: "#002244" + property color bgColor: "black" property real volume: 0.25 property bool perfMonitorsLogging: false property bool perfMonitorsVisible: false QtObject { id: d - property int itemHeight: 40 + property int itemHeight: root.height > root.width ? root.width / 10 : root.height / 10 property int buttonHeight: 0.8 * itemHeight - property int margins: 10 + property int margins: 5 } Loader { @@ -92,6 +91,9 @@ Rectangle { right: exitButton.left margins: d.margins } + bgColor: "#212121" + bgColorSelected: "#757575" + textColorSelected: "white" height: d.buttonHeight text: (root.source1 == "") ? "Select file 1" : root.source1 onClicked: fileBrowser1.show() @@ -105,6 +107,9 @@ Rectangle { right: exitButton.left margins: d.margins } + bgColor: "#212121" + bgColorSelected: "#757575" + textColorSelected: "white" height: d.buttonHeight text: (root.source2 == "") ? "Select file 2" : root.source2 onClicked: fileBrowser2.show() @@ -117,24 +122,57 @@ Rectangle { right: parent.right margins: d.margins } - width: 50 + bgColor: "#212121" + bgColorSelected: "#757575" + textColorSelected: "white" + width: parent.width / 10 height: d.buttonHeight text: "Exit" onClicked: Qt.quit() } + Row { + id: modes + anchors.top: openFile2Button.bottom + anchors.margins: 0 + anchors.topMargin: 5 + Button { + width: root.width / 2 + height: 0.8 * d.itemHeight + bgColor: "#212121" + radius: 0 + text: "Video Modes" + enabled: false + } + Button { + width: root.width / 2 + height: 0.8 * d.itemHeight + bgColor: "#212121" + radius: 0 + text: "Camera Modes" + enabled: false + } + } + + Rectangle { + id: divider + height: 1 + width: parent.width + color: "black" + anchors.top: modes.bottom + } + SceneSelectionPanel { id: sceneSelectionPanel itemHeight: d.itemHeight - color: "#004444" + color: "#212121" anchors { - top: openFile2Button.bottom + top: divider.bottom left: parent.left right: parent.right bottom: parent.bottom - margins: d.margins } - radius: 10 + radius: 0 onSceneSourceChanged: { sceneLoader.source = sceneSource var scene = null @@ -205,7 +243,9 @@ Rectangle { ErrorDialog { id: errorDialog - anchors.fill: parent + anchors.fill: root + dialogWidth: d.itemHeight * 5 + dialogHeight: d.itemHeight * 3 enabled: false } diff --git a/examples/multimedia/video/qmlvideo/qmlvideo.qrc b/examples/multimedia/video/qmlvideo/qmlvideo.qrc index 9471eb6b..6418215d 100644 --- a/examples/multimedia/video/qmlvideo/qmlvideo.qrc +++ b/examples/multimedia/video/qmlvideo/qmlvideo.qrc @@ -1,13 +1,8 @@ images/leaves.jpg - images/close.png images/folder.png - images/titlebar.png - images/titlebar.sci images/up.png - images/progress_handle.svg - images/progress_handle_pressed.svg qml/qmlvideo/Button.qml qml/qmlvideo/CameraBasic.qml qml/qmlvideo/CameraDrag.qml diff --git a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/ContentCamera.qml b/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/ContentCamera.qml deleted file mode 100644 index fcf43f4b..00000000 --- a/examples/multimedia/video/qmlvideofx/qml/qmlvideofx/ContentCamera.qml +++ /dev/null @@ -1,43 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the Qt Mobility Components. -** -** $QT_BEGIN_LICENSE:LGPL21$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 or version 3 as published by the Free -** Software Foundation and appearing in the file LICENSE.LGPLv21 and -** LICENSE.LGPLv3 included in the packaging of this file. Please review the -** following information to ensure the GNU Lesser General Public License -** requirements will be met: https://www.gnu.org/licenses/lgpl.html and -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -import QtQuick 2.0 -import QtMultimedia 5.0 - -VideoOutput { - source: camera - - Camera { - id: camera - } -} diff --git a/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc b/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc index f3ad2770..e7978e39 100644 --- a/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc +++ b/examples/multimedia/video/qmlvideofx/qmlvideofx.qrc @@ -3,7 +3,6 @@ images/qt-logo.png qml/qmlvideofx/Button.qml qml/qmlvideofx/Content.qml - qml/qmlvideofx/ContentCamera.qml qml/qmlvideofx/ContentImage.qml qml/qmlvideofx/ContentVideo.qml qml/qmlvideofx/Divider.qml From f9de9889f6b909968f31743ab60e66dd356f2a4d Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 25 Sep 2014 17:10:08 +0200 Subject: [PATCH 40/48] Use QCameraInfo in camera example. To replace QCamera::availableDevices(), which is deprecated since 5.3. Change-Id: I0b1bd6286ec78d6d26ce309d224369989d4a5063 Reviewed-by: Christian Stromme --- examples/multimediawidgets/camera/camera.cpp | 27 +++++++++----------- examples/multimediawidgets/camera/camera.h | 2 +- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/examples/multimediawidgets/camera/camera.cpp b/examples/multimediawidgets/camera/camera.cpp index 3afe5932..714de7a4 100644 --- a/examples/multimediawidgets/camera/camera.cpp +++ b/examples/multimediawidgets/camera/camera.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,8 @@ #include +Q_DECLARE_METATYPE(QCameraInfo) + Camera::Camera(QWidget *parent) : QMainWindow(parent), ui(new Ui::Camera), @@ -65,26 +68,23 @@ Camera::Camera(QWidget *parent) : ui->setupUi(this); //Camera devices: - QByteArray cameraDevice; QActionGroup *videoDevicesGroup = new QActionGroup(this); videoDevicesGroup->setExclusive(true); - foreach(const QByteArray &deviceName, QCamera::availableDevices()) { - QString description = camera->deviceDescription(deviceName); - QAction *videoDeviceAction = new QAction(description, videoDevicesGroup); + foreach (const QCameraInfo &cameraInfo, QCameraInfo::availableCameras()) { + QAction *videoDeviceAction = new QAction(cameraInfo.description(), videoDevicesGroup); videoDeviceAction->setCheckable(true); - videoDeviceAction->setData(QVariant(deviceName)); - if (cameraDevice.isEmpty()) { - cameraDevice = deviceName; + videoDeviceAction->setData(QVariant::fromValue(cameraInfo)); + if (cameraInfo == QCameraInfo::defaultCamera()) videoDeviceAction->setChecked(true); - } + ui->menuDevices->addAction(videoDeviceAction); } connect(videoDevicesGroup, SIGNAL(triggered(QAction*)), SLOT(updateCameraDevice(QAction*))); connect(ui->captureWidget, SIGNAL(currentChanged(int)), SLOT(updateCaptureMode())); - setCamera(cameraDevice); + setCamera(QCameraInfo::defaultCamera()); } Camera::~Camera() @@ -94,16 +94,13 @@ Camera::~Camera() delete camera; } -void Camera::setCamera(const QByteArray &cameraDevice) +void Camera::setCamera(const QCameraInfo &cameraInfo) { delete imageCapture; delete mediaRecorder; delete camera; - if (cameraDevice.isEmpty()) - camera = new QCamera; - else - camera = new QCamera(cameraDevice); + camera = new QCamera(cameraInfo); connect(camera, SIGNAL(stateChanged(QCamera::State)), this, SLOT(updateCameraState(QCamera::State))); connect(camera, SIGNAL(error(QCamera::Error)), this, SLOT(displayCameraError())); @@ -398,7 +395,7 @@ void Camera::displayCameraError() void Camera::updateCameraDevice(QAction *action) { - setCamera(action->data().toByteArray()); + setCamera(qvariant_cast(action->data())); } void Camera::displayViewfinder() diff --git a/examples/multimediawidgets/camera/camera.h b/examples/multimediawidgets/camera/camera.h index 52f03cbd..faa02ccd 100644 --- a/examples/multimediawidgets/camera/camera.h +++ b/examples/multimediawidgets/camera/camera.h @@ -60,7 +60,7 @@ public: ~Camera(); private slots: - void setCamera(const QByteArray &cameraDevice); + void setCamera(const QCameraInfo &cameraInfo); void startCamera(); void stopCamera(); From 5c30ed55ef41ea85232f63c7ee4fd52a4e7126e2 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Wed, 24 Sep 2014 16:40:21 +0200 Subject: [PATCH 41/48] Improve audiorecorder example. - Show actual recording location - Update control buttons depending on the recorder state instead of the status - Create audio levels using the actual audio format from the audio buffers. The format from the QMediaRecorder might not always contain the value actually used. Task-number: QTBUG-36154 Change-Id: I418b4472b0d984f47efb1a1813da6ef440ba9a40 Reviewed-by: Christian Stromme --- .../audiorecorder/audiorecorder.cpp | 57 +++++++++++-------- .../multimedia/audiorecorder/audiorecorder.h | 1 + 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/examples/multimedia/audiorecorder/audiorecorder.cpp b/examples/multimedia/audiorecorder/audiorecorder.cpp index 2291fed5..7d942255 100644 --- a/examples/multimedia/audiorecorder/audiorecorder.cpp +++ b/examples/multimedia/audiorecorder/audiorecorder.cpp @@ -114,6 +114,8 @@ AudioRecorder::AudioRecorder(QWidget *parent) : SLOT(updateProgress(qint64))); connect(audioRecorder, SIGNAL(statusChanged(QMediaRecorder::Status)), this, SLOT(updateStatus(QMediaRecorder::Status))); + connect(audioRecorder, SIGNAL(stateChanged(QMediaRecorder::State)), + this, SLOT(onStateChanged(QMediaRecorder::State))); connect(audioRecorder, SIGNAL(error(QMediaRecorder::Error)), this, SLOT(displayErrorMessage())); } @@ -138,47 +140,44 @@ void AudioRecorder::updateStatus(QMediaRecorder::Status status) switch (status) { case QMediaRecorder::RecordingStatus: - if (audioLevels.count() != audioRecorder->audioSettings().channelCount()) { - qDeleteAll(audioLevels); - audioLevels.clear(); - for (int i = 0; i < audioRecorder->audioSettings().channelCount(); ++i) { - QAudioLevel *level = new QAudioLevel(ui->centralwidget); - audioLevels.append(level); - ui->levelsLayout->addWidget(level); - } - } - - ui->recordButton->setText(tr("Stop")); - ui->pauseButton->setText(tr("Pause")); - if (audioRecorder->outputLocation().isEmpty()) - statusMessage = tr("Recording"); - else - statusMessage = tr("Recording to %1").arg( - audioRecorder->outputLocation().toString()); + statusMessage = tr("Recording to %1").arg(audioRecorder->actualLocation().toString()); break; case QMediaRecorder::PausedStatus: clearAudioLevels(); - ui->recordButton->setText(tr("Stop")); - ui->pauseButton->setText(tr("Resume")); statusMessage = tr("Paused"); break; case QMediaRecorder::UnloadedStatus: case QMediaRecorder::LoadedStatus: clearAudioLevels(); - ui->recordButton->setText(tr("Record")); - ui->pauseButton->setText(tr("Pause")); statusMessage = tr("Stopped"); default: break; } - ui->pauseButton->setEnabled(audioRecorder->state() - != QMediaRecorder::StoppedState); - if (audioRecorder->error() == QMediaRecorder::NoError) ui->statusbar->showMessage(statusMessage); } +void AudioRecorder::onStateChanged(QMediaRecorder::State state) +{ + switch (state) { + case QMediaRecorder::RecordingState: + ui->recordButton->setText(tr("Stop")); + ui->pauseButton->setText(tr("Pause")); + break; + case QMediaRecorder::PausedState: + ui->recordButton->setText(tr("Stop")); + ui->pauseButton->setText(tr("Resume")); + break; + case QMediaRecorder::StoppedState: + ui->recordButton->setText(tr("Record")); + ui->pauseButton->setText(tr("Pause")); + break; + } + + ui->pauseButton->setEnabled(audioRecorder->state() != QMediaRecorder::StoppedState); +} + static QVariant boxValue(const QComboBox *box) { int idx = box->currentIndex(); @@ -347,6 +346,16 @@ QVector getBufferLevels(const T *buffer, int frames, int channels) void AudioRecorder::processBuffer(const QAudioBuffer& buffer) { + if (audioLevels.count() != buffer.format().channelCount()) { + qDeleteAll(audioLevels); + audioLevels.clear(); + for (int i = 0; i < buffer.format().channelCount(); ++i) { + QAudioLevel *level = new QAudioLevel(ui->centralwidget); + audioLevels.append(level); + ui->levelsLayout->addWidget(level); + } + } + QVector levels = getBufferLevels(buffer); for (int i = 0; i < levels.count(); ++i) audioLevels.at(i)->setLevel(levels.at(i)); diff --git a/examples/multimedia/audiorecorder/audiorecorder.h b/examples/multimedia/audiorecorder/audiorecorder.h index 2c0a1e5e..9d013ae7 100644 --- a/examples/multimedia/audiorecorder/audiorecorder.h +++ b/examples/multimedia/audiorecorder/audiorecorder.h @@ -71,6 +71,7 @@ private slots: void toggleRecord(); void updateStatus(QMediaRecorder::Status); + void onStateChanged(QMediaRecorder::State); void updateProgress(qint64 pos); void displayErrorMessage(); From ec245921865eda2bc57397c1979c92fa10adfd98 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Fri, 19 Sep 2014 09:53:53 +0200 Subject: [PATCH 42/48] Android: fix some problems with the media player. - Correctly emit positionChanged signal. One of the problems with this was that QMediaPlayer automatically sends periodic position updates while playing a media. There's no need to have the same logic in the backend. - Seeking after reaching the end of the media now correctly works Auto tests included. Change-Id: I6d5ecbae6e05f94a8aac1a0834cf57427adf219b Reviewed-by: Christian Stromme --- .../multimedia/QtAndroidMediaPlayer.java | 20 -- .../qandroidmediaplayercontrol.cpp | 130 ++++----- .../mediaplayer/qandroidmediaplayercontrol.h | 1 + .../tst_qmediaplayerbackend.cpp | 246 ++++++++++++++++-- 4 files changed, 294 insertions(+), 103 deletions(-) diff --git a/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer.java b/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer.java index ade2517d..5e6630de 100644 --- a/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer.java +++ b/src/plugins/android/jar/src/org/qtproject/qt5/android/multimedia/QtAndroidMediaPlayer.java @@ -83,23 +83,6 @@ public class QtAndroidMediaPlayer private volatile int mState = State.Uninitialized; - private class ProgressWatcher - implements Runnable - { - @Override - public void run() - { - try { - while ((mState & (State.Started)) != 0) { - onProgressUpdateNative(getCurrentPosition(), mID); - Thread.sleep(1000); - } - } catch (final InterruptedException e) { - // Ignore - } - } - } - /** * MediaPlayer OnErrorListener */ @@ -257,8 +240,6 @@ public class QtAndroidMediaPlayer try { mMediaPlayer.start(); setState(State.Started); - Thread progressThread = new Thread(new ProgressWatcher()); - progressThread.start(); } catch (final IllegalStateException e) { Log.d(TAG, "" + e.getMessage()); } @@ -309,7 +290,6 @@ public class QtAndroidMediaPlayer try { mMediaPlayer.seekTo(msec); - onProgressUpdateNative(msec, mID); } catch (final IllegalStateException e) { Log.d(TAG, "" + e.getMessage()); } diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp index fb1c8b72..eae09c64 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.cpp @@ -86,6 +86,7 @@ QAndroidMediaPlayerControl::QAndroidMediaPlayerControl(QObject *parent) mPendingSetMedia(false), mPendingVolume(-1), mPendingMute(-1), + mReloadingMedia(false), mActiveStateChangeNotifiers(0) { connect(mMediaPlayer,SIGNAL(bufferingChanged(qint32)), @@ -138,17 +139,14 @@ qint64 QAndroidMediaPlayerControl::position() const if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia) return duration(); - if ((mState & (AndroidMediaPlayer::Idle - | AndroidMediaPlayer::Initialized - | AndroidMediaPlayer::Prepared + if ((mState & (AndroidMediaPlayer::Prepared | AndroidMediaPlayer::Started | AndroidMediaPlayer::Paused - | AndroidMediaPlayer::Stopped - | AndroidMediaPlayer::PlaybackCompleted)) == 0) { - return (mPendingPosition == -1) ? 0 : mPendingPosition; + | AndroidMediaPlayer::PlaybackCompleted))) { + return mMediaPlayer->getCurrentPosition(); } - return (mCurrentState == QMediaPlayer::StoppedState) ? 0 : mMediaPlayer->getCurrentPosition(); + return (mPendingPosition == -1) ? 0 : mPendingPosition; } void QAndroidMediaPlayerControl::setPosition(qint64 position) @@ -158,26 +156,25 @@ void QAndroidMediaPlayerControl::setPosition(qint64 position) const int seekPosition = (position > INT_MAX) ? INT_MAX : position; - if ((mState & (AndroidMediaPlayer::Prepared - | AndroidMediaPlayer::Started - | AndroidMediaPlayer::Paused - | AndroidMediaPlayer::PlaybackCompleted)) == 0) { - if (mPendingPosition != seekPosition) { - mPendingPosition = seekPosition; - Q_EMIT positionChanged(seekPosition); - } + if (seekPosition == this->position()) return; - } StateChangeNotifier notifier(this); if (mCurrentMediaStatus == QMediaPlayer::EndOfMedia) setMediaStatus(QMediaPlayer::LoadedMedia); - mMediaPlayer->seekTo(seekPosition); + if ((mState & (AndroidMediaPlayer::Prepared + | AndroidMediaPlayer::Started + | AndroidMediaPlayer::Paused + | AndroidMediaPlayer::PlaybackCompleted)) == 0) { + mPendingPosition = seekPosition; + } else { + mMediaPlayer->seekTo(seekPosition); - if (mPendingPosition != -1) { - mPendingPosition = -1; + if (mPendingPosition != -1) { + mPendingPosition = -1; + } } Q_EMIT positionChanged(seekPosition); @@ -310,9 +307,9 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, { StateChangeNotifier notifier(this); - const bool reloading = (mMediaContent == mediaContent); + mReloadingMedia = (mMediaContent == mediaContent); - if (!reloading) { + if (!mReloadingMedia) { mMediaContent = mediaContent; mMediaStream = stream; } @@ -321,43 +318,45 @@ void QAndroidMediaPlayerControl::setMedia(const QMediaContent &mediaContent, if ((mState & (AndroidMediaPlayer::Idle | AndroidMediaPlayer::Uninitialized)) == 0) mMediaPlayer->release(); + QString mediaPath; + if (mediaContent.isNull()) { setMediaStatus(QMediaPlayer::NoMedia); - return; - } - - 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; - } - - const QUrl url = mediaContent.canonicalUrl(); - QString mediaPath; - if (url.scheme() == QLatin1String("qrc")) { - const QString path = url.toString().mid(3); - mTempFile.reset(QTemporaryFile::createNativeFile(path)); - if (!mTempFile.isNull()) - mediaPath = QStringLiteral("file://") + mTempFile->fileName(); } else { - mediaPath = url.toString(); + 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; + } + + const QUrl url = mediaContent.canonicalUrl(); + if (url.scheme() == QLatin1String("qrc")) { + const QString path = url.toString().mid(3); + mTempFile.reset(QTemporaryFile::createNativeFile(path)); + if (!mTempFile.isNull()) + mediaPath = QStringLiteral("file://") + mTempFile->fileName(); + } else { + mediaPath = url.toString(); + } + + if (mVideoSize.isValid() && mVideoOutput) + mVideoOutput->setVideoSize(mVideoSize); + + if ((mMediaPlayer->display() == 0) && mVideoOutput) + mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); + mMediaPlayer->setDataSource(mediaPath); + mMediaPlayer->prepareAsync(); } - if (mVideoSize.isValid() && mVideoOutput) - mVideoOutput->setVideoSize(mVideoSize); - - if ((mMediaPlayer->display() == 0) && mVideoOutput) - mMediaPlayer->setDisplay(mVideoOutput->surfaceTexture()); - mMediaPlayer->setDataSource(mediaPath); - mMediaPlayer->prepareAsync(); - - if (!reloading) { + if (!mReloadingMedia) { Q_EMIT mediaChanged(mMediaContent); Q_EMIT actualMediaLocationChanged(mediaPath); } resetBufferingProgress(); + + mReloadingMedia = false; } void QAndroidMediaPlayerControl::setVideoOutput(QObject *videoOutput) @@ -567,7 +566,8 @@ void QAndroidMediaPlayerControl::onStateChanged(qint32 state) case AndroidMediaPlayer::Initialized: break; case AndroidMediaPlayer::Preparing: - setMediaStatus(QMediaPlayer::LoadingMedia); + if (!mReloadingMedia) + setMediaStatus(QMediaPlayer::LoadingMedia); break; case AndroidMediaPlayer::Prepared: setMediaStatus(QMediaPlayer::LoadedMedia); @@ -588,6 +588,7 @@ void QAndroidMediaPlayerControl::onStateChanged(qint32 state) } else { setMediaStatus(QMediaPlayer::BufferedMedia); } + Q_EMIT positionChanged(position()); break; case AndroidMediaPlayer::Paused: setState(QMediaPlayer::PausedState); @@ -596,27 +597,32 @@ void QAndroidMediaPlayerControl::onStateChanged(qint32 state) setState(QMediaPlayer::StoppedState); setMediaStatus(QMediaPlayer::UnknownMediaStatus); mMediaPlayer->release(); + Q_EMIT positionChanged(0); break; case AndroidMediaPlayer::Stopped: setState(QMediaPlayer::StoppedState); setMediaStatus(QMediaPlayer::LoadedMedia); - setPosition(0); + Q_EMIT positionChanged(0); break; case AndroidMediaPlayer::PlaybackCompleted: setState(QMediaPlayer::StoppedState); - setPosition(0); setMediaStatus(QMediaPlayer::EndOfMedia); break; case AndroidMediaPlayer::Uninitialized: - // reset some properties - resetBufferingProgress(); - mPendingPosition = -1; - mPendingSetMedia = false; - mPendingState = -1; + // reset some properties (unless we reload the same media) + if (!mReloadingMedia) { + resetBufferingProgress(); + mPendingPosition = -1; + mPendingSetMedia = false; + mPendingState = -1; - setAudioAvailable(false); - setVideoAvailable(false); - setSeekable(true); + Q_EMIT durationChanged(0); + Q_EMIT positionChanged(0); + + setAudioAvailable(false); + setVideoAvailable(false); + setSeekable(true); + } break; default: break; @@ -655,13 +661,13 @@ 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 durationChanged(duration()); - - mCurrentMediaStatus = status; + Q_EMIT positionChanged(position()); updateBufferStatus(); } diff --git a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h index 7de0c2dc..64b88f49 100644 --- a/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h +++ b/src/plugins/android/src/mediaplayer/qandroidmediaplayercontrol.h @@ -111,6 +111,7 @@ private: bool mPendingSetMedia; int mPendingVolume; int mPendingMute; + bool mReloadingMedia; QScopedPointer mTempFile; int mActiveStateChangeNotifiers; diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp index 5109d305..b4e2f1f1 100644 --- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp +++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp @@ -71,6 +71,7 @@ private slots: void volumeAcrossFiles(); void initialVolume(); void seekPauseSeek(); + void seekInStoppedState(); void subsequentPlayback(); void probes(); void playlist(); @@ -79,10 +80,11 @@ private slots: private: QMediaContent selectVideoFile(const QStringList& mediaCandidates); - QMediaContent selectSoundFile(const QStringList& mediaCandidates); + QMediaContent selectMediaFile(const QStringList& mediaCandidates); //one second local wav file QMediaContent localWavFile; + QMediaContent localWavFile2; QMediaContent localVideoFile; QMediaContent localCompressedSoundFile; @@ -169,17 +171,17 @@ QMediaContent tst_QMediaPlayerBackend::selectVideoFile(const QStringList& mediaC return QMediaContent(); } -QMediaContent tst_QMediaPlayerBackend::selectSoundFile(const QStringList& mediaCandidates) +QMediaContent tst_QMediaPlayerBackend::selectMediaFile(const QStringList& mediaCandidates) { QMediaPlayer player; QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error))); foreach (QString s, mediaCandidates) { - QFileInfo soundFile(s); - if (!soundFile.exists()) + QFileInfo mediaFile(s); + if (!mediaFile.exists()) continue; - QMediaContent media = QMediaContent(QUrl::fromLocalFile(soundFile.absoluteFilePath())); + QMediaContent media = QMediaContent(QUrl::fromLocalFile(mediaFile.absoluteFilePath())); player.setMedia(media); player.play(); @@ -205,17 +207,24 @@ void tst_QMediaPlayerBackend::initTestCase() localWavFile = QMediaContent(QUrl::fromLocalFile(wavFile.absoluteFilePath())); + const QString testFileName2 = QFINDTESTDATA("testdata/_test.wav"); + QFileInfo wavFile2(testFileName2); + + QVERIFY(wavFile2.exists()); + + localWavFile2 = QMediaContent(QUrl::fromLocalFile(wavFile2.absoluteFilePath())); + qRegisterMetaType(); QStringList mediaCandidates; mediaCandidates << QFINDTESTDATA("testdata/colors.ogv"); mediaCandidates << QFINDTESTDATA("testdata/colors.mp4"); - localVideoFile = selectVideoFile(mediaCandidates); + localVideoFile = selectMediaFile(mediaCandidates); mediaCandidates.clear(); mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mkv"); mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mp3"); - localCompressedSoundFile = selectSoundFile(mediaCandidates); + localCompressedSoundFile = selectMediaFile(mediaCandidates); qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); } @@ -239,6 +248,7 @@ void tst_QMediaPlayerBackend::loadMedia() QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent))); + QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent))); player.setMedia(localWavFile); @@ -247,11 +257,13 @@ void tst_QMediaPlayerBackend::loadMedia() QVERIFY(player.mediaStatus() != QMediaPlayer::NoMedia); QVERIFY(player.mediaStatus() != QMediaPlayer::InvalidMedia); QVERIFY(player.media() == localWavFile); + QVERIFY(player.currentMedia() == localWavFile); QCOMPARE(stateSpy.count(), 0); QVERIFY(statusSpy.count() > 0); QCOMPARE(mediaSpy.count(), 1); QCOMPARE(mediaSpy.last()[0].value(), localWavFile); + QCOMPARE(currentMediaSpy.last()[0].value(), localWavFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); @@ -267,6 +279,7 @@ void tst_QMediaPlayerBackend::unloadMedia() QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State))); QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent))); + QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent))); QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); QSignalSpy durationSpy(&player, SIGNAL(positionChanged(qint64))); @@ -285,6 +298,7 @@ void tst_QMediaPlayerBackend::unloadMedia() stateSpy.clear(); statusSpy.clear(); mediaSpy.clear(); + currentMediaSpy.clear(); positionSpy.clear(); durationSpy.clear(); @@ -295,10 +309,12 @@ void tst_QMediaPlayerBackend::unloadMedia() QCOMPARE(player.state(), QMediaPlayer::StoppedState); QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); QCOMPARE(player.media(), QMediaContent()); + QCOMPARE(player.currentMedia(), QMediaContent()); QVERIFY(!stateSpy.isEmpty()); QVERIFY(!statusSpy.isEmpty()); QVERIFY(!mediaSpy.isEmpty()); + QVERIFY(!currentMediaSpy.isEmpty()); QVERIFY(!positionSpy.isEmpty()); } @@ -327,7 +343,7 @@ void tst_QMediaPlayerBackend::playPauseStop() QTRY_VERIFY(statusSpy.count() > 0 && statusSpy.last()[0].value() == QMediaPlayer::BufferedMedia); - QTRY_VERIFY(player.position() > 0); + QTRY_VERIFY(player.position() > 100); QVERIFY(player.duration() > 0); QVERIFY(positionSpy.count() > 0); QVERIFY(positionSpy.last()[0].value() > 0); @@ -361,6 +377,63 @@ void tst_QMediaPlayerBackend::playPauseStop() QCOMPARE(player.position(), qint64(0)); QCOMPARE(positionSpy.last()[0].value(), qint64(0)); QVERIFY(player.duration() > 0); + + stateSpy.clear(); + statusSpy.clear(); + positionSpy.clear(); + + player.play(); + + QCOMPARE(player.state(), QMediaPlayer::PlayingState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QCOMPARE(stateSpy.count(), 1); + QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::PlayingState); + QCOMPARE(statusSpy.count(), 1); // Should not go through Loading again when play -> stop -> play + QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::BufferedMedia); + + player.stop(); + stateSpy.clear(); + statusSpy.clear(); + positionSpy.clear(); + + player.setMedia(localWavFile2); + + QTRY_VERIFY(statusSpy.count() > 0); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::LoadedMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 0); + + player.play(); + + QTRY_VERIFY(player.position() > 100); + + player.setMedia(localWavFile); + + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::LoadedMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); + QCOMPARE(player.position(), 0); + QCOMPARE(positionSpy.last()[0].value(), 0); + + stateSpy.clear(); + statusSpy.clear(); + positionSpy.clear(); + + player.play(); + + QTRY_VERIFY(player.position() > 100); + + player.setMedia(QMediaContent()); + + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); + QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::NoMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); + QCOMPARE(player.position(), 0); + QCOMPARE(positionSpy.last()[0].value(), 0); + QCOMPARE(player.duration(), 0); } @@ -383,17 +456,25 @@ void tst_QMediaPlayerBackend::processEOS() QVERIFY(statusSpy.count() > 0); QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 2); + QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); //at EOS the position stays at the end of file - QVERIFY(player.position() > 900); + QCOMPARE(player.position(), player.duration()); + QVERIFY(positionSpy.count() > 0); + QCOMPARE(positionSpy.last()[0].value(), player.duration()); stateSpy.clear(); statusSpy.clear(); + positionSpy.clear(); player.play(); //position is reset to start QTRY_VERIFY(player.position() < 100); + QVERIFY(positionSpy.count() > 0); + QCOMPARE(positionSpy.first()[0].value(), 0); QCOMPARE(player.state(), QMediaPlayer::PlayingState); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); @@ -406,13 +487,16 @@ void tst_QMediaPlayerBackend::processEOS() player.setPosition(900); //wait up to 5 seconds for EOS QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QVERIFY(statusSpy.count() > 0); + QCOMPARE(statusSpy.last()[0].value(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 2); + QCOMPARE(stateSpy.last()[0].value(), QMediaPlayer::StoppedState); - //ensure the positionChanged() signal is emitted - QVERIFY(positionSpy.count() > 0); - - QCOMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); //position stays at the end of file - QVERIFY(player.position() > 900); + QCOMPARE(player.position(), player.duration()); + QVERIFY(positionSpy.count() > 0); + QCOMPARE(positionSpy.last()[0].value(), player.duration()); //after setPosition EndOfMedia status should be reset to Loaded stateSpy.clear(); @@ -608,7 +692,7 @@ void tst_QMediaPlayerBackend::initialVolume() void tst_QMediaPlayerBackend::seekPauseSeek() { if (localVideoFile.isNull()) - QSKIP("Video format is not supported"); + QSKIP("No supported video file"); QMediaPlayer player; @@ -674,6 +758,125 @@ void tst_QMediaPlayerBackend::seekPauseSeek() } } +void tst_QMediaPlayerBackend::seekInStoppedState() +{ + if (localVideoFile.isNull()) + QSKIP("No supported video file"); + + QMediaPlayer player; + player.setNotifyInterval(500); + + QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State))); + QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); + + player.setMedia(localVideoFile); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(player.position(), 0); + QVERIFY(player.isSeekable()); + + stateSpy.clear(); + positionSpy.clear(); + + qint64 position = 5000; + player.setPosition(position); + + QTRY_VERIFY(qAbs(player.position() - position) < qint64(500)); + QCOMPARE(positionSpy.count(), 1); + QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(500)); + + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 0); + + QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + positionSpy.clear(); + + player.play(); + + QCOMPARE(player.state(), QMediaPlayer::PlayingState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QVERIFY(qAbs(player.position() - position) < qint64(500)); + + QTest::qWait(2000); + // Check that it never played from the beginning + QVERIFY(player.position() > (position - 500)); + for (int i = 0; i < positionSpy.count(); ++i) + QVERIFY(positionSpy.at(i)[0].value() > (position - 500)); + + // ------ + // Same tests but after play() --> stop() + + player.stop(); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + QCOMPARE(player.position(), 0); + + stateSpy.clear(); + positionSpy.clear(); + + player.setPosition(position); + + QTRY_VERIFY(qAbs(player.position() - position) < qint64(500)); + QCOMPARE(positionSpy.count(), 1); + QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(500)); + + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 0); + + QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + positionSpy.clear(); + + player.play(); + + QCOMPARE(player.state(), QMediaPlayer::PlayingState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QVERIFY(qAbs(player.position() - position) < qint64(500)); + + QTest::qWait(2000); + // Check that it never played from the beginning + QVERIFY(player.position() > (position - 500)); + for (int i = 0; i < positionSpy.count(); ++i) + QVERIFY(positionSpy.at(i)[0].value() > (position - 500)); + + // ------ + // Same tests but after reaching the end of the media + + player.setPosition(player.duration() - 500); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(player.position(), player.duration()); + + stateSpy.clear(); + positionSpy.clear(); + + player.setPosition(position); + + QTRY_VERIFY(qAbs(player.position() - position) < qint64(500)); + QCOMPARE(positionSpy.count(), 1); + QVERIFY(qAbs(positionSpy.last()[0].value() - position) < qint64(500)); + + QCOMPARE(player.state(), QMediaPlayer::StoppedState); + QCOMPARE(stateSpy.count(), 0); + + QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + positionSpy.clear(); + + player.play(); + + QCOMPARE(player.state(), QMediaPlayer::PlayingState); + QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QVERIFY(qAbs(player.position() - position) < qint64(500)); + + QTest::qWait(2000); + // Check that it never played from the beginning + QVERIFY(player.position() > (position - 500)); + for (int i = 0; i < positionSpy.count(); ++i) + QVERIFY(positionSpy.at(i)[0].value() > (position - 500)); +} + void tst_QMediaPlayerBackend::subsequentPlayback() { #ifdef Q_OS_LINUX @@ -717,7 +920,7 @@ void tst_QMediaPlayerBackend::subsequentPlayback() void tst_QMediaPlayerBackend::probes() { if (localVideoFile.isNull()) - QSKIP("Video format is not supported"); + QSKIP("No supported video file"); QMediaPlayer *player = new QMediaPlayer; @@ -733,8 +936,9 @@ void tst_QMediaPlayerBackend::probes() connect(audioProbe, SIGNAL(audioBufferProbed(QAudioBuffer)), &probeHandler, SLOT(processBuffer(QAudioBuffer))); connect(audioProbe, SIGNAL(flush()), &probeHandler, SLOT(flushAudio())); - QVERIFY(videoProbe->setSource(player)); - QVERIFY(audioProbe->setSource(player)); + if (!videoProbe->setSource(player)) + QSKIP("QVideoProbe is not supported"); + audioProbe->setSource(player); player->setMedia(localVideoFile); QTRY_COMPARE(player->mediaStatus(), QMediaPlayer::LoadedMedia); @@ -831,7 +1035,7 @@ void tst_QMediaPlayerBackend::playlist() errorSpy.clear(); // <<< Invalid2 - 1st pass >>> - fileInfo.setFile((QFINDTESTDATA("testdata/invalid_media2.m3u"))); + fileInfo.setFile(QFINDTESTDATA("/testdata/invalid_media2.m3u")); player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); player.play(); @@ -864,7 +1068,7 @@ void tst_QMediaPlayerBackend::playlist() errorSpy.clear(); // <<< Recursive - 1st pass >>> - fileInfo.setFile((QFINDTESTDATA("testdata/recursive_master.m3u"))); + fileInfo.setFile(QFINDTESTDATA("testdata/recursive_master.m3u")); player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); player.play(); @@ -929,7 +1133,7 @@ void tst_QMediaPlayerBackend::surfaceTest() { // 25 fps video file if (localVideoFile.isNull()) - QSKIP("Video format is not supported"); + QSKIP("No supported video file"); QFETCH(QList, formatsList); From 99a55585e961818b3153e99b8368cba00fc7c108 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Fri, 19 Sep 2014 12:01:08 +0200 Subject: [PATCH 43/48] Add metadata test for qmediaplayerbackend auto-test. Change-Id: I419496293b11b6a941af08e9709595458b7ca423 Reviewed-by: Andrew den Exter --- .../testdata/nokia-tune.mp3 | Bin 61472 -> 62715 bytes .../tst_qmediaplayerbackend.cpp | 38 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 index 674c834057401870ad4412ea28429d106418ad63..2435f65b8be6bd2402e51859a411e6ef5402d9f6 100644 GIT binary patch delta 1296 zcmZ4Rfcf`J=6X*TV-^M=;3y373^4*SxEUCj{qnOj6BR;A^HM`x{hWb|P delta 24 dcmezUl6k=cW-d<`V-^Mw5Su73wz!(*0{~-i2PXgk diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp index b4e2f1f1..0a1441cd 100644 --- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp +++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp @@ -39,6 +39,7 @@ #include "qaudioprobe.h" #include "qvideoprobe.h" #include +#include //TESTED_COMPONENT=src/multimedia @@ -77,6 +78,7 @@ private slots: void playlist(); void surfaceTest_data(); void surfaceTest(); + void metadata(); private: QMediaContent selectVideoFile(const QStringList& mediaCandidates); @@ -87,6 +89,7 @@ private: QMediaContent localWavFile2; QMediaContent localVideoFile; QMediaContent localCompressedSoundFile; + QMediaContent localFileWithMetadata; bool m_inCISystem; }; @@ -226,6 +229,8 @@ void tst_QMediaPlayerBackend::initTestCase() mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mp3"); localCompressedSoundFile = selectMediaFile(mediaCandidates); + localFileWithMetadata = selectMediaFile(QStringList() << QFINDTESTDATA("testdata/nokia-tune.mp3")); + qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); } @@ -1147,6 +1152,39 @@ void tst_QMediaPlayerBackend::surfaceTest() QVERIFY(surface.m_totalFrames >= 25); } +void tst_QMediaPlayerBackend::metadata() +{ + if (localFileWithMetadata.isNull()) + QSKIP("No supported media file"); + + QMediaPlayer player; + + QSignalSpy metadataAvailableSpy(&player, SIGNAL(metaDataAvailableChanged(bool))); + QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged())); + + player.setMedia(localFileWithMetadata); + + QTRY_VERIFY(player.isMetaDataAvailable()); + QCOMPARE(metadataAvailableSpy.count(), 1); + QVERIFY(metadataAvailableSpy.last()[0].toBool()); + QVERIFY(metadataChangedSpy.count() > 0); + + QCOMPARE(player.metaData(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune")); + QCOMPARE(player.metaData(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist")); + QCOMPARE(player.metaData(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum")); + + metadataAvailableSpy.clear(); + metadataChangedSpy.clear(); + + player.setMedia(QMediaContent()); + + QVERIFY(!player.isMetaDataAvailable()); + QCOMPARE(metadataAvailableSpy.count(), 1); + QVERIFY(!metadataAvailableSpy.last()[0].toBool()); + QCOMPARE(metadataChangedSpy.count(), 1); + QVERIFY(player.availableMetaData().isEmpty()); +} + TestVideoSurface::TestVideoSurface(bool storeFrames): m_totalFrames(0), m_storeFrames(storeFrames) From e97fc77890895791546a42ea4a769f23cfbfe73d Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Mon, 22 Sep 2014 14:03:34 +0200 Subject: [PATCH 44/48] GStreamer: fix QMediaPlayer metadata signals. - metaDataAvailableChanged was never emitted - metaDataChanged was not emitted when clearing the current media Change-Id: Ide05056450171a87aeb018be7e0bdea136341946 Reviewed-by: Andrew den Exter --- .../gstreamer/mediaplayer/qgstreamermetadataprovider.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp index e9c922e2..b470a2c5 100644 --- a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp @@ -164,6 +164,11 @@ void QGstreamerMetaDataProvider::updateTags() } } + if (oldTags.isEmpty() != m_tags.isEmpty()) { + emit metaDataAvailableChanged(isMetaDataAvailable()); + changed = true; + } + if (changed) emit metaDataChanged(); } From ecc3c3507ffb42ee96d7977db081797ae8398806 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Mon, 22 Sep 2014 15:06:14 +0200 Subject: [PATCH 45/48] GStreamer: fix artist-related metadata keys. QMediaMetaData::AlbumArtist now maps to GST_TAG_ALBUM_ARTIST and QMediaMetaData::ContributingArtist to GST_TAG_ARTIST. Change-Id: Ifa2cb90ee8ae09beaee572ad113c05776e699432 Reviewed-by: Andrew den Exter --- .../gstreamer/mediaplayer/qgstreamermetadataprovider.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp index b470a2c5..8ae3a78d 100644 --- a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp @@ -76,8 +76,10 @@ static const QGstreamerMetaDataKeyLookup *qt_gstreamerMetaDataKeys() // Music metadataKeys->insert(GST_TAG_ALBUM, QMediaMetaData::AlbumTitle); - metadataKeys->insert(GST_TAG_ARTIST, QMediaMetaData::AlbumArtist); - metadataKeys->insert(GST_TAG_PERFORMER, QMediaMetaData::ContributingArtist); +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 25) + metadataKeys->insert(GST_TAG_ALBUM_ARTIST, QMediaMetaData::AlbumArtist); +#endif + metadataKeys->insert(GST_TAG_ARTIST, QMediaMetaData::ContributingArtist); #if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) metadataKeys->insert(GST_TAG_COMPOSER, QMediaMetaData::Composer); #endif From 7f1f5edf4daf86d4e4860fb256f3b03987d8c663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomi=20Korpip=C3=A4=C3=A4?= Date: Fri, 26 Sep 2014 13:27:55 +0300 Subject: [PATCH 46/48] Updated snapshots for qmlvideo example documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task-number: QTBUG-36287 Change-Id: I9da63560ba92a572d6d3943cfdade0b609469ace Reviewed-by: Topi Reiniö --- .../video/doc/images/qmlvideo-menu.jpg | Bin 0 -> 21959 bytes .../video/doc/images/qmlvideo-menu.png | Bin 64550 -> 0 bytes .../video/doc/images/qmlvideo-overlay.jpg | Bin 0 -> 23787 bytes .../video/doc/images/qmlvideo-overlay.png | Bin 65432 -> 0 bytes examples/multimedia/video/doc/src/qmlvideo.qdoc | 4 ++-- 5 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 examples/multimedia/video/doc/images/qmlvideo-menu.jpg delete mode 100644 examples/multimedia/video/doc/images/qmlvideo-menu.png create mode 100644 examples/multimedia/video/doc/images/qmlvideo-overlay.jpg delete mode 100644 examples/multimedia/video/doc/images/qmlvideo-overlay.png diff --git a/examples/multimedia/video/doc/images/qmlvideo-menu.jpg b/examples/multimedia/video/doc/images/qmlvideo-menu.jpg new file mode 100644 index 0000000000000000000000000000000000000000..54ab877a1881611b3331868d42864111b4b35087 GIT binary patch literal 21959 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 literal 0 HcmV?d00001 diff --git a/examples/multimedia/video/doc/images/qmlvideo-menu.png b/examples/multimedia/video/doc/images/qmlvideo-menu.png deleted file mode 100644 index b2d773319515b369e279fc093fd1d24d7eea579a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64550 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

Gr3+-+zIFL~dPHLRmfagqz>LahRWeeDUbv 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 diff --git a/examples/multimedia/video/doc/src/qmlvideo.qdoc b/examples/multimedia/video/doc/src/qmlvideo.qdoc index 1e80cd17..5ffb542f 100644 --- a/examples/multimedia/video/doc/src/qmlvideo.qdoc +++ b/examples/multimedia/video/doc/src/qmlvideo.qdoc @@ -46,7 +46,7 @@ The following image shows the application executing the video-overlay scene, which creates a dummy overlay item (just a semi-transparent \l{Rectangle}), which moves across the \l{VideoOutput} item. -\image qmlvideo-overlay.png +\image qmlvideo-overlay.jpg \include examples-run.qdocinc @@ -67,7 +67,7 @@ the following items: average over the past second. \endlist -\image qmlvideo-menu.png +\image qmlvideo-menu.jpg Each scene in the flickable list is implemented in its own QML file - for example the video-basic scene (which just displays a static \l{VideoOutput} From f02d9e934322fbf9af8a5503c1bda37552988b2b Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Tue, 30 Sep 2014 16:41:25 +0200 Subject: [PATCH 47/48] AVFoundation: fix media player video rendering in QML. The AVPlayerLayer was set on the QVideoRendererControl before its geometry was updated, causing the renderer control to display frames with an invalid size. Change-Id: I90e18dce69d4b48a3d7932d44a7eab4fd443f1fb Reviewed-by: Christian Stromme --- .../mediaplayer/avfmediaplayersession.h | 2 -- .../mediaplayer/avfmediaplayersession.mm | 21 ++++--------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h index c850f45d..5157a857 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.h @@ -97,8 +97,6 @@ public Q_SLOTS: void processPositionChange(); void processMediaLoadError(); - void processCurrentItemChanged(); - Q_SIGNALS: void positionChanged(qint64 position); void durationChanged(qint64 duration); diff --git a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm index d6f0607a..73e9d764 100644 --- a/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm +++ b/src/plugins/avfoundation/mediaplayer/avfmediaplayersession.mm @@ -340,9 +340,6 @@ static void *AVFMediaPlayerSessionObserverCurrentItemObservationContext = &AVFMe AVPlayerItem *newPlayerItem = [change objectForKey:NSKeyValueChangeNewKey]; if (m_playerItem != newPlayerItem) m_playerItem = newPlayerItem; - - if (self.session) - QMetaObject::invokeMethod(m_session, "processCurrentItemChanged", Qt::AutoConnection); } else { @@ -806,6 +803,10 @@ void AVFMediaPlayerSession::processLoadStateChange() playerLayer.bounds = CGRectMake(0.0f, 0.0f, videoTrack.naturalSize.width, videoTrack.naturalSize.height); + + if (m_videoOutput && m_state != QMediaPlayer::StoppedState) { + m_videoOutput->setLayer(playerLayer); + } } } @@ -836,17 +837,3 @@ void AVFMediaPlayerSession::processMediaLoadError() Q_EMIT mediaStatusChanged(m_mediaStatus = QMediaPlayer::InvalidMedia); Q_EMIT stateChanged(m_state = QMediaPlayer::StoppedState); } - -void AVFMediaPlayerSession::processCurrentItemChanged() -{ -#ifdef QT_DEBUG_AVF - qDebug() << Q_FUNC_INFO; -#endif - - AVPlayerLayer *playerLayer = [(AVFMediaPlayerSessionObserver*)m_observer playerLayer]; - - if (m_videoOutput && m_state != QMediaPlayer::StoppedState) { - m_videoOutput->setLayer(playerLayer); - } - -} From ca94dc79b6f0e57ba7446a87c70398a178fbcac8 Mon Sep 17 00:00:00 2001 From: Yoann Lopes Date: Thu, 2 Oct 2014 14:21:20 +0200 Subject: [PATCH 48/48] GStreamer: fix QMediaRecorder::duration() when recording with a camera To get the recording duration, we were using the camerabin's position, which represents the time since it was started, not the time it's been recording to a file. We now retrieve the camerabin's filesink position. Change-Id: I68eeb25d1718666288655d22deea23e25de73b90 Reviewed-by: Andrew den Exter --- .../gstreamer/camerabin/camerabinsession.cpp | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.cpp b/src/plugins/gstreamer/camerabin/camerabinsession.cpp index 850f8c16..01978397 100644 --- a/src/plugins/gstreamer/camerabin/camerabinsession.cpp +++ b/src/plugins/gstreamer/camerabin/camerabinsession.cpp @@ -98,6 +98,8 @@ #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 @@ -721,13 +723,19 @@ void CameraBinSession::updateBusyStatus(GObject *o, GParamSpec *p, gpointer d) qint64 CameraBinSession::duration() const { - GstFormat format = GST_FORMAT_TIME; - gint64 duration = 0; + 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 = gst_element_query_position(fileSink, &format, &duration); + gst_object_unref(GST_OBJECT(fileSink)); + if (ret) + return duration / 1000000; + } + } - if ( m_camerabin && gst_element_query_position(m_camerabin, &format, &duration)) - return duration / 1000000; - else - return 0; + return 0; } bool CameraBinSession::isMuted() const