From 40393bf9e36aac18cb907a71dee9e826577c73c3 Mon Sep 17 00:00:00 2001 From: Mohammed Sameer Date: Thu, 18 Jul 2013 02:42:25 +0300 Subject: [PATCH] Initial implementation of our own VideoPlayer declarative item --- cameraplus.pro | 1 - declarative/camera.cpp | 2 + declarative/cameraconfig.cpp | 4 + declarative/declarative.pro | 4 +- declarative/plugin.cpp | 5 + declarative/videoplayer.cpp | 351 +++++++++++++++++++++++++++ declarative/videoplayer.h | 99 ++++++++ lib/qtcamviewfinderrenderermeego.cpp | 7 + qml/CameraView.qml | 1 + qml/PipelineManager.qml | 3 + qml/PostCaptureItem.qml | 4 +- qml/PostCaptureView.qml | 6 +- qml/VideoPlayerPage.qml | 14 +- src/cameraresources.cpp | 7 + src/cameraresources.h | 1 + 15 files changed, 498 insertions(+), 11 deletions(-) create mode 100644 declarative/videoplayer.cpp create mode 100644 declarative/videoplayer.h diff --git a/cameraplus.pro b/cameraplus.pro index beadc23..e00e15d 100644 --- a/cameraplus.pro +++ b/cameraplus.pro @@ -1,4 +1,3 @@ TEMPLATE = subdirs CONFIG += ordered SUBDIRS = lib declarative src - diff --git a/declarative/camera.cpp b/declarative/camera.cpp index 86ccaa4..f4e4a63 100644 --- a/declarative/camera.cpp +++ b/declarative/camera.cpp @@ -75,6 +75,8 @@ Camera::Camera(QDeclarativeItem *parent) : m_videoTorch(0), m_config(new CameraConfig(this)) { + m_config->componentComplete(); + QObject::connect(m_vf, SIGNAL(renderAreaChanged()), this, SIGNAL(renderAreaChanged())); QObject::connect(m_vf, SIGNAL(videoResolutionChanged()), this, SIGNAL(videoResolutionChanged())); QObject::connect(m_vf, SIGNAL(renderingEnabledChanged()), this, SIGNAL(renderingEnabledChanged())); diff --git a/declarative/cameraconfig.cpp b/declarative/cameraconfig.cpp index b247d62..6085ed5 100644 --- a/declarative/cameraconfig.cpp +++ b/declarative/cameraconfig.cpp @@ -59,6 +59,10 @@ void CameraConfig::classBegin() { } void CameraConfig::componentComplete() { + if (m_config) { + return; + } + if (m_path.isEmpty()) { m_config = new QtCamConfig(this); } diff --git a/declarative/declarative.pro b/declarative/declarative.pro index 8b8c3a3..06a0a9e 100644 --- a/declarative/declarative.pro +++ b/declarative/declarative.pro @@ -17,7 +17,7 @@ HEADERS += plugin.h previewprovider.h camera.h mode.h imagemode.h videomode.h \ flickerreduction.h videomute.h metadata.h imagesettings.h \ imageresolutionmodel.h videosettings.h videoresolutionmodel.h \ notificationscontainer.h sounds.h focus.h autofocus.h \ - roi.h cameraconfig.h + roi.h cameraconfig.h videoplayer.h SOURCES += plugin.cpp previewprovider.cpp camera.cpp mode.cpp imagemode.cpp videomode.cpp \ zoom.cpp flash.cpp scene.cpp evcomp.cpp videotorch.cpp whitebalance.cpp \ @@ -25,7 +25,7 @@ SOURCES += plugin.cpp previewprovider.cpp camera.cpp mode.cpp imagemode.cpp vide flickerreduction.cpp videomute.cpp metadata.cpp imagesettings.cpp \ imageresolutionmodel.cpp videosettings.cpp videoresolutionmodel.cpp \ notificationscontainer.cpp sounds.cpp focus.cpp autofocus.cpp \ - roi.cpp cameraconfig.cpp + roi.cpp cameraconfig.cpp videoplayer.cpp HEADERS += declarativeqtcameranotifications.h diff --git a/declarative/plugin.cpp b/declarative/plugin.cpp index 56c23e2..1676e60 100644 --- a/declarative/plugin.cpp +++ b/declarative/plugin.cpp @@ -47,6 +47,7 @@ #include "declarativeqtcameranotifications.h" #include "sounds.h" #include "cameraconfig.h" +#include "videoplayer.h" #include #define MAJOR 1 @@ -68,6 +69,8 @@ void Plugin::initializeEngine(QDeclarativeEngine *engine, const char *uri) { } void Plugin::registerTypes(const char *uri) { + Q_ASSERT(QLatin1String(uri) == QLatin1String("QtCamera")); + qmlRegisterType(uri, MAJOR, MINOR, "Camera"); qmlRegisterType(uri, MAJOR, MINOR, "ImageMode"); qmlRegisterType(uri, MAJOR, MINOR, "VideoMode"); @@ -103,6 +106,8 @@ void Plugin::registerTypes(const char *uri) { qmlRegisterType(); qmlRegisterType(uri, MAJOR, MINOR, "CameraConfig"); + + qmlRegisterType("QtCameraExtras", MAJOR, MINOR, "VideoPlayer"); } Q_EXPORT_PLUGIN2(declarativeqtcamera, Plugin); diff --git a/declarative/videoplayer.cpp b/declarative/videoplayer.cpp new file mode 100644 index 0000000..29cfea4 --- /dev/null +++ b/declarative/videoplayer.cpp @@ -0,0 +1,351 @@ +/*! + * This file is part of CameraPlus. + * + * Copyright (C) 2012-2013 Mohammed Sameer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "videoplayer.h" +#include +#include "qtcamgraphicsviewfinder.h" +#include "cameraconfig.h" +#include + +VideoPlayer::VideoPlayer(QDeclarativeItem *parent) : + QDeclarativeItem(parent), + m_config(0), + m_vf(0), + m_bin(0), + m_state(VideoPlayer::StateStopped), + m_timer(new QTimer(this)), + m_pos(0) { + + m_timer->setSingleShot(false); + m_timer->setInterval(500); + QObject::connect(m_timer, SIGNAL(timeout()), this, SIGNAL(positionChanged())); +} + +VideoPlayer::~VideoPlayer() { + stop(); + + if (m_bin) { + gst_object_unref(m_bin); + m_bin = 0; + } +} + +void VideoPlayer::componentComplete() { + +} + +void VideoPlayer::classBegin() { + m_bin = gst_element_factory_make ("playbin2", "VideoPlayerBin"); + if (!m_bin) { + qmlInfo(this) << "Failed to create playbin2"; + return; + } + + g_object_set (m_bin, "flags", 99, NULL); + + GstElement *elem = gst_element_factory_make("pulsesink", "VideoPlayerPulseSink"); + if (!elem) { + qmlInfo(this) << "Failed to create pulsesink"; + } + else { + // TODO: properties on sink + g_object_set (m_bin, "audio-sink", elem, NULL); + } + + GstBus *bus = gst_element_get_bus(m_bin); + gst_bus_add_watch(bus, bus_call, this); + gst_object_unref(bus); +} + +QUrl VideoPlayer::source() const { + return m_url; +} + +void VideoPlayer::setSource(const QUrl& source) { + if (m_url != source) { + m_url = source; + emit sourceChanged(); + } +} + +CameraConfig *VideoPlayer::cameraConfig() const { + return m_config; +} + +void VideoPlayer::setCameraConfig(CameraConfig *config) { + if (m_config && m_config != config) { + qmlInfo(this) << "Cannot reset CameraConfig"; + return; + } + + if (!config) { + qmlInfo(this) << "CameraConfig cannot be empty"; + return; + } + + if (m_config != config) { + m_config = config; + // TODO: We need fence sync here. + m_vf = new QtCamGraphicsViewfinder(m_config->config(), this); + m_vf->resize(QSizeF(width(), height())); + emit cameraConfigChanged(); + } + + if (m_bin) { + g_object_set(m_bin, "video-sink", m_vf->sinkElement(), NULL); + } +} + +qint64 VideoPlayer::duration() const { + if (!m_bin) { + return 0; + } + + GstFormat format = GST_FORMAT_TIME; + qint64 dur = 0; + if (!gst_element_query_duration(m_bin, &format, &dur)) { + qmlInfo(this) << "Failed to query pipeline duration"; + return 0; + } + + if (format != GST_FORMAT_TIME) { + qmlInfo(this) << "Pipeline format is not time"; + return 0; + } + + dur /= 1000000; + + return dur; +} + +qint64 VideoPlayer::position() { + if (!m_bin) { + return 0; + } + + GstFormat format = GST_FORMAT_TIME; + qint64 pos = 0; + if (!gst_element_query_position(m_bin, &format, &pos)) { + qmlInfo(this) << "Failed to query pipeline position"; + return m_pos; + } + + if (format != GST_FORMAT_TIME) { + qmlInfo(this) << "Pipeline format is not time"; + return m_pos; + } + + pos /= 1000000; + + m_pos = pos; + + return pos; +} + +void VideoPlayer::setPosition(qint64 position) { + seek(position); +} + +bool VideoPlayer::pause() { + return setState(VideoPlayer::StatePaused); +} + +bool VideoPlayer::play() { + return setState(VideoPlayer::StatePlaying); +} + +bool VideoPlayer::seek(qint64 offset) { + if (!m_bin) { + qmlInfo(this) << "no playbin2"; + return false; + } + + qint64 pos = offset; + + offset *= 1000000; + + GstSeekFlags flags = (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE); + gboolean ret = gst_element_seek_simple (m_bin, GST_FORMAT_TIME, + flags, offset); + + if (ret) { + m_pos = pos; + + return TRUE; + } + + return FALSE; +} + +bool VideoPlayer::stop() { + return setState(VideoPlayer::StateStopped); +} + +void VideoPlayer::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) { + QDeclarativeItem::geometryChanged(newGeometry, oldGeometry); + + if (m_vf) { + m_vf->resize(newGeometry.size()); + } +} + +VideoPlayer::State VideoPlayer::state() const { + return m_state; +} + +bool VideoPlayer::setState(const VideoPlayer::State& state) { + if (state == m_state) { + return true; + } + + if (!m_bin) { + qmlInfo(this) << "no playbin2"; + return false; + } + + if (state == VideoPlayer::StatePaused) { + m_timer->stop(); + + // Set uri if needed: + if (m_state == VideoPlayer::StateStopped) { + const char *uri = m_url.toString().toUtf8().constData(); + g_object_set(m_bin, "uri", uri, NULL); + } + + if (gst_element_set_state(m_bin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "error setting pipeline to PAUSED"; + return false; + } + + GstState st; + if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE) + == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "setting pipeline to PAUSED failed"; + return false; + } + + if (st != GST_STATE_PAUSED) { + qmlInfo(this) << "pipeline failed to transition to to PAUSED state"; + return false; + } + + m_state = state; + emit stateChanged(); + + return true; + } + else if (state == VideoPlayer::StatePlaying) { + // Set uri if needed: + if (m_state == VideoPlayer::StateStopped) { + const char *uri = m_url.toString().toUtf8().constData(); + g_object_set(m_bin, "uri", uri, NULL); + } + + if (gst_element_set_state(m_bin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "error setting pipeline to PLAYING"; + return false; + } + + GstState st; + if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE) + == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "setting pipeline to PLAYING failed"; + return false; + } + + if (st != GST_STATE_PLAYING) { + qmlInfo(this) << "pipeline failed to transition to to PLAYING state"; + return false; + } + + m_state = state; + emit stateChanged(); + + emit durationChanged(); + emit positionChanged(); + + m_timer->start(); + return true; + } + else { + m_timer->stop(); + m_pos = 0; + + if (gst_element_set_state(m_bin, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "error setting pipeline to NULL"; + return false; + } + + GstState st; + if (gst_element_get_state(m_bin, &st, NULL, GST_CLOCK_TIME_NONE) + == GST_STATE_CHANGE_FAILURE) { + qmlInfo(this) << "setting pipeline to NULL failed"; + return false; + } + + if (st != GST_STATE_NULL) { + qmlInfo(this) << "pipeline failed to transition to to NULL state"; + return false; + } + + m_state = state; + emit stateChanged(); + + emit durationChanged(); + emit positionChanged(); + + return true; + } +} + +gboolean VideoPlayer::bus_call(GstBus *bus, GstMessage *msg, gpointer data) { + Q_UNUSED(bus); + + VideoPlayer *that = (VideoPlayer *) data; + + gchar *debug = NULL; + GError *err = NULL; + + switch (GST_MESSAGE_TYPE(msg)) { + case GST_MESSAGE_EOS: + that->stop(); + break; + + case GST_MESSAGE_ERROR: + gst_message_parse_error (msg, &err, &debug); + + emit that->error(err->message, err->code, debug); + that->stop(); + if (err) { + g_error_free (err); + } + + if (debug) { + g_free (debug); + } + + break; + + default: + break; + } + + return TRUE; +} diff --git a/declarative/videoplayer.h b/declarative/videoplayer.h new file mode 100644 index 0000000..388b621 --- /dev/null +++ b/declarative/videoplayer.h @@ -0,0 +1,99 @@ +// -*- c++ -*- + +/*! + * This file is part of CameraPlus. + * + * Copyright (C) 2012-2013 Mohammed Sameer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef VIDEO_PLAYER_H +#define VIDEO_PLAYER_H + +#include +#include + +class CameraConfig; +class QtCamGraphicsViewfinder; + +class VideoPlayer : public QDeclarativeItem { + Q_OBJECT + + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged); + Q_PROPERTY(CameraConfig *cameraConfig READ cameraConfig WRITE setCameraConfig NOTIFY cameraConfigChanged); + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged); + Q_PROPERTY(qint64 position READ position WRITE setPosition NOTIFY positionChanged); + Q_PROPERTY(State state READ state NOTIFY stateChanged); + Q_ENUMS(State); + +public: + VideoPlayer(QDeclarativeItem *parent = 0); + ~VideoPlayer(); + + virtual void componentComplete(); + virtual void classBegin(); + + QUrl source() const; + void setSource(const QUrl& source); + + CameraConfig *cameraConfig() const; + void setCameraConfig(CameraConfig *config); + + qint64 duration() const; + qint64 position(); + void setPosition(qint64 position); + + Q_INVOKABLE bool pause(); + Q_INVOKABLE bool play(); + Q_INVOKABLE bool seek(qint64 offset); + Q_INVOKABLE bool stop(); + + typedef enum { + StateStopped, + StatePaused, + StatePlaying, + } State; + + State state() const; + +signals: + void sourceChanged(); + void cameraConfigChanged(); + + void durationChanged(); + void positionChanged(); + void error(const QString& message, int code, const QString& debug); + void stateChanged(); + +protected: + void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry); + +private: + static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data); + + bool setState(const State& state); + + CameraConfig *m_config; + QtCamGraphicsViewfinder *m_vf; + QUrl m_url; + + GstElement *m_bin; + State m_state; + QTimer *m_timer; + qint64 m_pos; +}; + +#endif /* VIDEO_PLAYER_H */ diff --git a/lib/qtcamviewfinderrenderermeego.cpp b/lib/qtcamviewfinderrenderermeego.cpp index ea2807d..f996c9e 100644 --- a/lib/qtcamviewfinderrenderermeego.cpp +++ b/lib/qtcamviewfinderrenderermeego.cpp @@ -172,7 +172,14 @@ GstElement *QtCamViewfinderRendererMeeGo::sinkElement() { g_object_set(G_OBJECT(m_sink), "x-display", d, "use-framebuffer-memory", TRUE, NULL); m_dpy = eglGetDisplay((EGLNativeDisplayType)d); + if (m_dpy == EGL_NO_DISPLAY) { + qCritical() << "Failed to obtain EGL Display"; + } + EGLContext context = eglGetCurrentContext(); + if (context == EGL_NO_CONTEXT) { + qCritical() << "Failed to obtain EGL context"; + } g_object_set(G_OBJECT(m_sink), "egl-display", m_dpy, "egl-context", context, NULL); diff --git a/qml/CameraView.qml b/qml/CameraView.qml index 13b2ec4..48b2855 100644 --- a/qml/CameraView.qml +++ b/qml/CameraView.qml @@ -24,6 +24,7 @@ import QtQuick 1.1 import QtCamera 1.0 import CameraPlus 1.0 +// TODO: reset reticle and roi when we stop camera or change mode Camera { id: cam diff --git a/qml/PipelineManager.qml b/qml/PipelineManager.qml index 458885a..d328711 100644 --- a/qml/PipelineManager.qml +++ b/qml/PipelineManager.qml @@ -65,6 +65,9 @@ Item { } else if (!policy.acquire(currentItem.policyMode)) { console.log("Failed to acquire policy resources") return + } else if (currentItem.policyMode == CameraResources.Player) { + currentPolicyMode = CameraResources.Player + camera.stop(true) } else if (!camera.start()) { showError(qsTr("Failed to start camera. Please restart the application.")) } else { diff --git a/qml/PostCaptureItem.qml b/qml/PostCaptureItem.qml index af18fdb..6cd193e 100644 --- a/qml/PostCaptureItem.qml +++ b/qml/PostCaptureItem.qml @@ -35,7 +35,9 @@ Item { function startPlayback() { loader.source = Qt.resolvedUrl("VideoPlayerPage.qml") loader.item.source = itemData.url - loader.item.play() + if (!loader.item.play()) { + loader.source = "" + } } Loader { diff --git a/qml/PostCaptureView.qml b/qml/PostCaptureView.qml index e82d861..2d304b7 100644 --- a/qml/PostCaptureView.qml +++ b/qml/PostCaptureView.qml @@ -27,9 +27,9 @@ import QtCamera 1.0 Item { property bool pressed: view.currentItem ? view.currentItem.playing : false - property int policyMode: view.currentItem && view.currentItem.playing ? CameraResources.None : - settings.mode == Camera.VideoMode ? CameraResources.Video - : CameraResources.Image + property int policyMode: view.currentItem && view.currentItem.playing ? + CameraResources.Player : settings.mode == Camera.VideoMode ? CameraResources.Video : + CameraResources.Image property bool available: view.currentItem ? view.currentItem.itemData.available : false Component.onCompleted: postCaptureModel.reload() diff --git a/qml/VideoPlayerPage.qml b/qml/VideoPlayerPage.qml index fce5101..5fb702a 100644 --- a/qml/VideoPlayerPage.qml +++ b/qml/VideoPlayerPage.qml @@ -22,8 +22,9 @@ import QtQuick 1.1 import com.nokia.meego 1.1 -import QtMultimediaKit 1.1 import CameraPlus 1.0 +import QtCamera 1.0 +import QtCameraExtras 1.0 // TODO: error reporting @@ -34,7 +35,7 @@ Item { property alias source: video.source function play() { - video.play() + return video.play() } MouseArea { @@ -53,9 +54,10 @@ Item { onTriggered: toolBar.show = false } - Video { + VideoPlayer { id: video anchors.fill: parent + cameraConfig: cam.cameraConfig function toggle() { if (!video.paused) { @@ -65,7 +67,11 @@ Item { } } - onStopped: page.finished() + onStateChanged: { + if (state == VideoPlayer.StateStopped) { + page.finished() + } + } } Connections { diff --git a/src/cameraresources.cpp b/src/cameraresources.cpp index 03db383..4e936e5 100644 --- a/src/cameraresources.cpp +++ b/src/cameraresources.cpp @@ -216,6 +216,13 @@ void CameraResourcesWorker::acquire(bool *ok, const CameraResources::Mode& mode) << ResourcePolicy::AudioPlaybackType); break; + case CameraResources::Player: + *ok = updateSet(QList() + << ResourcePolicy::VideoPlaybackType + << ResourcePolicy::AudioPlaybackType, + QList()); + break; + default: qWarning() << "Unknown mode" << mode; diff --git a/src/cameraresources.h b/src/cameraresources.h index 5183bb4..e31b9c1 100644 --- a/src/cameraresources.h +++ b/src/cameraresources.h @@ -45,6 +45,7 @@ public: Image, Video, Recording, + Player, } Mode; CameraResources(QObject *parent = 0); -- 2.25.1