summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Kiagiadakis <george.kiagiadakis@collabora.co.uk>2011-01-20 01:03:46 +0200
committerGeorge Kiagiadakis <george.kiagiadakis@collabora.co.uk>2011-01-20 01:03:46 +0200
commite1838187cb3fef36e825f12e212f22e0a4d4eeb7 (patch)
treeb6ddc1b20ce5f37cc1183771c56b27d944e4aa82
parent3e3da859a7d4b2bfba2c6869fab3ebf5a4299be7 (diff)
VideoWidget: add support for watching a pipeline and drop the broken support for autoplug elements.
The previous support for autoplug elements was broken, as for example an autovideosink on a playbin2 does not create the child element on the main thread, which causes trouble for us. The new method of watching the pipeline will work nicely on most pipelines, like playbin2, and follows gstreamer's documentation to the letter, which guarantees it will work correctly, as long as there is only one sink on the pipeline.
-rw-r--r--src/QGst/Ui/videowidget.cpp168
-rw-r--r--src/QGst/Ui/videowidget.h75
-rw-r--r--tests/manual/CMakeLists.txt3
-rw-r--r--tests/manual/videowidgetpipelinetest.cpp72
-rw-r--r--tests/manual/videowidgetpipelinetest.ui42
-rw-r--r--tests/manual/videowidgettest.cpp2
6 files changed, 298 insertions, 64 deletions
diff --git a/src/QGst/Ui/videowidget.cpp b/src/QGst/Ui/videowidget.cpp
index 659176d..f1d93e0 100644
--- a/src/QGst/Ui/videowidget.cpp
+++ b/src/QGst/Ui/videowidget.cpp
@@ -1,5 +1,7 @@
/*
- Copyright (C) 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
+ Copyright (C) 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
+ Copyright (C) 2011 Collabora Ltd.
+ @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
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
@@ -16,9 +18,13 @@
*/
#include "videowidget.h"
#include "../xoverlay.h"
-#include "../childproxy.h"
+#include "../pipeline.h"
+#include "../bus.h"
+#include "../message.h"
#include "../../QGlib/connect.h"
#include <QtCore/QDebug>
+#include <QtCore/QMutex>
+#include <QtCore/QThread>
#include <QtGui/QPainter>
#include <QtGui/QPaintEvent>
#include <QtGui/QResizeEvent>
@@ -40,12 +46,11 @@ public:
class XOverlayRenderer : public QObject, public AbstractRenderer
{
public:
- XOverlayRenderer(const XOverlayPtr & sink, QWidget *parent)
- : QObject(parent), m_sink(sink)
+ XOverlayRenderer(QWidget *parent)
+ : QObject(parent)
{
- WId windowId = widget()->winId(); //create a new X window (if we are on X11 with alien widgets)
+ m_windowId = widget()->winId(); //create a new X window (if we are on X11 with alien widgets)
QApplication::syncX(); //inform other applications about the new window (on X11)
- m_sink->setWindowHandle(windowId);
widget()->installEventFilter(this);
widget()->setAttribute(Qt::WA_NoSystemBackground, true);
@@ -55,20 +60,40 @@ public:
virtual ~XOverlayRenderer()
{
- m_sink->setWindowHandle(0);
+ if (m_sink) {
+ m_sink->setWindowHandle(0);
+ }
widget()->removeEventFilter(this);
widget()->setAttribute(Qt::WA_NoSystemBackground, false);
widget()->setAttribute(Qt::WA_PaintOnScreen, false);
widget()->update();
}
- virtual ElementPtr videoSink() const { return m_sink.dynamicCast<Element>(); }
+ void setVideoSink(const XOverlayPtr & sink)
+ {
+ QMutexLocker l(&m_sinkMutex);
+ if (m_sink) {
+ m_sink->setWindowHandle(0);
+ }
+ m_sink = sink;
+ if (m_sink) {
+ m_sink->setWindowHandle(m_windowId);
+ }
+ }
+
+ virtual ElementPtr videoSink() const
+ {
+ QMutexLocker l(&m_sinkMutex);
+ return m_sink.dynamicCast<Element>();
+ }
+protected:
virtual bool eventFilter(QObject *filteredObject, QEvent *event)
{
if (filteredObject == parent() && event->type() == QEvent::Paint) {
- State currentState;
- videoSink()->getState(&currentState, NULL, 0);
+ QMutexLocker l(&m_sinkMutex);
+ State currentState = m_sink ? m_sink.dynamicCast<Element>()->currentState() : StateNull;
+
if (currentState == StatePlaying || currentState == StatePaused) {
m_sink->expose();
} else {
@@ -83,6 +108,8 @@ public:
private:
inline QWidget *widget() { return static_cast<QWidget*>(parent()); }
+ WId m_windowId;
+ mutable QMutex m_sinkMutex;
XOverlayPtr m_sink;
};
@@ -109,49 +136,52 @@ private:
};
-class ProxyRenderer : public QObject, public AbstractRenderer
+class PipelineWatch : public QObject, public AbstractRenderer
{
public:
- ProxyRenderer(const ChildProxyPtr & sink, QWidget *parent)
- : m_renderer(NULL), m_parent(parent), m_sink(sink)
+ PipelineWatch(const PipelinePtr & pipeline, QWidget *parent)
+ : QObject(parent), m_renderer(new XOverlayRenderer(parent)), m_pipeline(pipeline)
{
- for (uint i=0; m_renderer == NULL && i<sink->childrenCount(); i++) {
- childAdded(sink->childByIndex(i));
- }
-
- QGlib::connect(sink, "child-added", this, &ProxyRenderer::childAdded);
- QGlib::connect(sink, "child-removed", this, &ProxyRenderer::childRemoved);
+ pipeline->bus()->enableSyncMessageEmission();
+ QGlib::connect(pipeline->bus(), "sync-message",
+ this, &PipelineWatch::onBusSyncMessage);
}
- virtual ~ProxyRenderer()
+ virtual ~PipelineWatch()
{
- QGlib::disconnect(m_sink, 0, this);
+ m_pipeline->bus()->disableSyncMessageEmission();
+ delete m_renderer;
}
- virtual ElementPtr videoSink() const { return m_sink.dynamicCast<Element>(); }
+ virtual ElementPtr videoSink() const { return m_renderer->videoSink(); }
- void childAdded(const QGlib::ObjectPtr & child)
- {
- if (!m_renderer) {
- ElementPtr element = child.dynamicCast<Element>();
- if (element) {
- m_renderer = AbstractRenderer::create(element, m_parent);
- }
- }
- }
+ void releaseSink() { m_renderer->setVideoSink(XOverlayPtr()); }
- void childRemoved(const QGlib::ObjectPtr & child)
+private:
+ void onBusSyncMessage(const MessagePtr & msg)
{
- if (m_renderer && m_renderer->videoSink() == child) {
- delete m_renderer;
- m_renderer = NULL;
+ switch (msg->type()) {
+ case MessageElement:
+ if (msg->internalStructure()->name() == QLatin1String("prepare-xwindow-id")) {
+ XOverlayPtr overlay = msg->source().dynamicCast<XOverlay>();
+ m_renderer->setVideoSink(overlay);
+ }
+ break;
+ case MessageStateChanged:
+ //release the sink when it goes back to null state
+ if (msg.staticCast<StateChangedMessage>()->newState() == StateNull &&
+ msg->source() == m_renderer->videoSink())
+ {
+ releaseSink();
+ }
+ default:
+ break;
}
}
private:
- AbstractRenderer *m_renderer;
- QWidget *m_parent;
- ChildProxyPtr m_sink;
+ XOverlayRenderer *m_renderer;
+ PipelinePtr m_pipeline;
};
@@ -160,7 +190,9 @@ AbstractRenderer *AbstractRenderer::create(const ElementPtr & sink, QWidget *vid
#if !defined(Q_WS_QWS)
XOverlayPtr overlay = sink.dynamicCast<XOverlay>();
if (overlay) {
- return new XOverlayRenderer(overlay, videoWidget);
+ XOverlayRenderer *r = new XOverlayRenderer(videoWidget);
+ r->setVideoSink(overlay);
+ return r;
}
#endif
@@ -168,11 +200,6 @@ AbstractRenderer *AbstractRenderer::create(const ElementPtr & sink, QWidget *vid
return new QWidgetVideoSinkRenderer(sink, videoWidget);
}
- ChildProxyPtr childProxy = sink.dynamicCast<ChildProxy>();
- if (childProxy) {
- return new ProxyRenderer(childProxy, videoWidget);
- }
-
return NULL;
}
@@ -194,17 +221,58 @@ ElementPtr VideoWidget::videoSink() const
void VideoWidget::setVideoSink(const ElementPtr & sink)
{
- delete d;
- d = NULL;
+ if (!sink) {
+ releaseVideoSink();
+ return;
+ }
+
+ Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
+ Q_ASSERT(d == NULL);
+
+ d = AbstractRenderer::create(sink, this);
+
+ if (!d) {
+ qCritical() << "QGst::Ui::VideoWidget: Could not construct a renderer for the specified element";
+ }
+}
- if (sink) {
- d = AbstractRenderer::create(sink, this);
+void VideoWidget::releaseVideoSink()
+{
+ Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
- if (!d) {
- qCritical() << "QGst::Ui::VideoWidget: Could not construct a renderer for the specified element";
+ if (d) {
+ PipelineWatch *pw = dynamic_cast<PipelineWatch*>(d);
+ if (pw) {
+ pw->releaseSink();
+ } else {
+ delete d;
+ d = NULL;
}
}
}
+void VideoWidget::watchPipeline(const PipelinePtr & pipeline)
+{
+ if (!pipeline) {
+ stopPipelineWatch();
+ return;
+ }
+
+ Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
+ Q_ASSERT(d == NULL);
+
+ d = new PipelineWatch(pipeline, this);
+}
+
+void VideoWidget::stopPipelineWatch()
+{
+ Q_ASSERT(QThread::currentThread() == QApplication::instance()->thread());
+
+ if (dynamic_cast<PipelineWatch*>(d)) {
+ delete d;
+ d = NULL;
+ }
+}
+
} //namespace Ui
} //namespace QGst
diff --git a/src/QGst/Ui/videowidget.h b/src/QGst/Ui/videowidget.h
index 1709800..6b12e82 100644
--- a/src/QGst/Ui/videowidget.h
+++ b/src/QGst/Ui/videowidget.h
@@ -1,5 +1,7 @@
/*
- Copyright (C) 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
+ Copyright (C) 2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
+ Copyright (C) 2011 Collabora Ltd.
+ @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
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
@@ -30,14 +32,31 @@ class AbstractRenderer;
* \brief A generic video widget that can embed a video sink
*
* This widget allows you to embed a video sink on a Qt user interface.
- * It supports a wide range of video sinks, including all the sinks that
- * implement the XOverlay interface, the "qwidgetvideosink" and autoplug
- * video sinks, such as "autovideosink" and "gconfvideosink". Because the
- * autoplug sinks are actualy bins, this widget can also accept any kind
- * of bin (to be precise, anything that implements the ChildProxy interface).
- * However, it is not recommended to use it with bins, unless you are
- * completely sure that there can be no child bins and there can be only
- * one video sink element in this bin.
+ *
+ * There are two ways of using this widget:
+ * \li Create a video sink yourself and set it with the setVideoSink() method.
+ * This will work for all sinks that implement the XOverlay interface, plus
+ * the "qwidgetvideosink", which paints directly on the widget.
+ * \li Create a pipeline and let the widget watch the pipeline using the
+ * watchPipeline() method. This will cause the widget to watch the bus for
+ * the "prepare-xwindow-id" that all XOverlay sinks send right before
+ * creating a window and will embed any sink that sends this message.
+ * You need to make sure however that there can only be one video sink in
+ * this pipeline. If there are more than one, you should handle them yourself
+ * and set them with the setVideoSink() method to different widgets.
+ *
+ * Nearly all the methods of this class \em must be called from Qt's GUI thread.
+ * Also, you cannot start or stop watching a pipeline that is in PLAYING state.
+ * Doing so may crash the widget.
+ *
+ * This widget will always keep a strong reference to the element that it is given,
+ * whether this is a video sink or a pipeline. If you want to destroy this element
+ * or pipeline, you need to call releaseVideoSink() or stopPipelineWatch() respectively.
+ *
+ * \note Autoplug video sinks such as autovideosink are not supported due
+ * to the complexity of handling them correctly. If you wish to use autovideosink,
+ * you can either set it to READY state and get its child XOverlay element
+ * or just watch the pipeline in which you plug it.
*/
class QTGSTREAMERUI_EXPORT VideoWidget : public QWidget
{
@@ -46,14 +65,44 @@ public:
VideoWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
virtual ~VideoWidget();
- /*! Returns the video sink element that was set using setVideoSink(),
- * or a null ElementPtr if no sink has been set. */
+
+ /*! Returns the video sink element that is currently providing this
+ * widget's image, or a null ElementPtr if no sink has been set.
+ */
ElementPtr videoSink() const;
- /*! Sets the video sink element that is going to be embedded. You can safely
- * pass a null ElementPtr here to remove the previously embedded sink. */
+ /*! Sets the video sink element that is going to be embedded.
+ * Any sink that implements the XOverlay interface will work, as well as "qwidgetvideosink".
+ * \note
+ * \li This method \em must be called from Qt's GUI thread.
+ * \li Passing a null ElementPtr has the same effect as calling releaseVideoSink().
+ * \li You cannot set a new sink if the previous one has not been released first.
+ */
void setVideoSink(const ElementPtr & sink);
+ /*! Detaches the current video sink from the widget and drops any references to it.
+ * \note This method \em must be called from Qt's GUI thread.
+ */
+ void releaseVideoSink();
+
+
+ /*! Starts watching a pipeline for any attached XOverlay sinks. If such
+ * a sink is found while the pipeline prepares itself to start playing,
+ * it is embedded to the widget.
+ * \note
+ * \li This method \em must be called from Qt's GUI thread.
+ * \li Passing a null PipelinePtr has the same effect as calling stopPipelineWatch().
+ * \li You cannot start watching a new pipeline if you don't stop watching the previous
+ * one first with stopPipelineWatch().
+ */
+ void watchPipeline(const PipelinePtr & pipeline);
+
+ /*! Stops watching a pipeline and also detaches the sink
+ * that was discovered in the pipeline, if any.
+ * \note This method \em must be called from Qt's GUI thread.
+ */
+ void stopPipelineWatch();
+
private:
AbstractRenderer *d;
};
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index f79f76f..5d4ac2b 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -11,6 +11,9 @@ qt4_wrap_ui(VIDEOWIDGETTEST_UI videowidgettest.ui)
automoc4_add_executable(videowidgettest videowidgettest.cpp ${VIDEOWIDGETTEST_UI})
target_link_libraries(videowidgettest ${QTGSTREAMER_UI_LIBRARIES})
+qt4_wrap_ui(VIDEOWIDGETPIPELINETEST_UI videowidgetpipelinetest.ui)
+automoc4_add_executable(videowidgetpipelinetest videowidgetpipelinetest.cpp ${VIDEOWIDGETPIPELINETEST_UI})
+target_link_libraries(videowidgetpipelinetest ${QTGSTREAMER_UI_LIBRARIES})
qt4_wrap_ui(VIDEOORIENTATIONTEST_UI videoorientationtest.ui)
automoc4_add_executable(videoorientationtest videoorientationtest.cpp ${VIDEOORIENTATIONTEST_UI})
diff --git a/tests/manual/videowidgetpipelinetest.cpp b/tests/manual/videowidgetpipelinetest.cpp
new file mode 100644
index 0000000..7b7732e
--- /dev/null
+++ b/tests/manual/videowidgetpipelinetest.cpp
@@ -0,0 +1,72 @@
+/*
+ Copyright (C) 2011 Collabora Ltd.
+ @author George Kiagiadakis <george.kiagiadakis@collabora.co.uk>
+
+ 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 program 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 General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "ui_videowidgetpipelinetest.h"
+#include <QGst/Init>
+#include <QGst/Pipeline>
+#include <QGst/ElementFactory>
+#include <QtGui/QApplication>
+
+class VideoWidgetPipelineTest : public QWidget
+{
+ Q_OBJECT
+public:
+ VideoWidgetPipelineTest(QWidget* parent = 0, Qt::WindowFlags f = 0);
+
+private Q_SLOTS:
+ void on_button_clicked();
+
+private:
+ Ui::VideoWidgetPipelineTest m_ui;
+ QGst::PipelinePtr m_pipeline;
+};
+
+VideoWidgetPipelineTest::VideoWidgetPipelineTest(QWidget *parent, Qt::WindowFlags f)
+ : QWidget(parent, f)
+{
+ m_ui.setupUi(this);
+
+ m_pipeline = QGst::ElementFactory::make("playbin2").dynamicCast<QGst::Pipeline>();
+ if (!m_pipeline) {
+ throw std::runtime_error("Unable to create a playbin2 pipeline");
+ }
+
+ m_ui.videoWidget->watchPipeline(m_pipeline);
+}
+
+void VideoWidgetPipelineTest::on_button_clicked()
+{
+ if (m_pipeline->currentState() == QGst::StateNull) {
+ m_pipeline->setProperty("uri", m_ui.lineEdit->text());
+ m_pipeline->setState(QGst::StatePlaying);
+ } else {
+ m_pipeline->setState(QGst::StateNull);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ QGst::init(&argc, &argv);
+
+ VideoWidgetPipelineTest test;
+ test.show();
+
+ return app.exec();
+}
+
+#include "videowidgetpipelinetest.moc" \ No newline at end of file
diff --git a/tests/manual/videowidgetpipelinetest.ui b/tests/manual/videowidgetpipelinetest.ui
new file mode 100644
index 0000000..9c8770d
--- /dev/null
+++ b/tests/manual/videowidgetpipelinetest.ui
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>VideoWidgetPipelineTest</class>
+ <widget class="QWidget" name="VideoWidgetPipelineTest">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>VideoWidgetPipelineTest</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0" colspan="2">
+ <widget class="QGst::Ui::VideoWidget" name="videoWidget" native="true"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLineEdit" name="lineEdit"/>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="button">
+ <property name="text">
+ <string>Play/Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>QGst::Ui::VideoWidget</class>
+ <extends>QWidget</extends>
+ <header location="global">QGst/Ui/VideoWidget</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/manual/videowidgettest.cpp b/tests/manual/videowidgettest.cpp
index 5a9b53a..6d41cf5 100644
--- a/tests/manual/videowidgettest.cpp
+++ b/tests/manual/videowidgettest.cpp
@@ -68,7 +68,7 @@ void VideoWidgetTest::onButtonClicked(int id)
if (currentState == QGst::StateNull) {
if (m_sink) {
- m_ui.videoWidget->setVideoSink(QGst::ElementPtr());
+ m_ui.videoWidget->releaseVideoSink();;
m_pipeline->remove(m_sink);
}