diff options
Diffstat (limited to 'src/gsttools/qgstvideorenderersink.cpp')
-rw-r--r-- | src/gsttools/qgstvideorenderersink.cpp | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/src/gsttools/qgstvideorenderersink.cpp b/src/gsttools/qgstvideorenderersink.cpp new file mode 100644 index 000000000..1102c2a1c --- /dev/null +++ b/src/gsttools/qgstvideorenderersink.cpp @@ -0,0 +1,605 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Jolla Ltd. +** 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 <qabstractvideosurface.h> +#include <qvideoframe.h> +#include <QDebug> +#include <QMap> +#include <QThread> +#include <QEvent> +#include <QCoreApplication> + +#include <private/qmediapluginloader_p.h> +#include "qgstvideobuffer_p.h" + +#include "qgstvideorenderersink_p.h" + +#include <gst/video/video.h> + +#include "qgstutils_p.h" + +//#define DEBUG_VIDEO_SURFACE_SINK + +QT_BEGIN_NAMESPACE + +QGstDefaultVideoRenderer::QGstDefaultVideoRenderer() + : m_flushed(true) +{ +} + +QGstDefaultVideoRenderer::~QGstDefaultVideoRenderer() +{ +} + +GstCaps *QGstDefaultVideoRenderer::getCaps(QAbstractVideoSurface *surface) +{ + return QGstUtils::capsForFormats(surface->supportedPixelFormats()); +} + +bool QGstDefaultVideoRenderer::start(QAbstractVideoSurface *surface, GstCaps *caps) +{ + m_flushed = true; + m_format = QGstUtils::formatForCaps(caps, &m_videoInfo); + + return m_format.isValid() && surface->start(m_format); +} + +void QGstDefaultVideoRenderer::stop(QAbstractVideoSurface *surface) +{ + m_flushed = true; + if (surface) + surface->stop(); +} + +bool QGstDefaultVideoRenderer::present(QAbstractVideoSurface *surface, GstBuffer *buffer) +{ + m_flushed = false; + QVideoFrame frame( + new QGstVideoBuffer(buffer, m_videoInfo), + m_format.frameSize(), + m_format.pixelFormat()); + QGstUtils::setFrameTimeStamps(&frame, buffer); + + return surface->present(frame); +} + +void QGstDefaultVideoRenderer::flush(QAbstractVideoSurface *surface) +{ + if (surface && !m_flushed) + surface->present(QVideoFrame()); + m_flushed = true; +} + +bool QGstDefaultVideoRenderer::proposeAllocation(GstQuery *) +{ + return true; +} + +Q_GLOBAL_STATIC_WITH_ARGS(QMediaPluginLoader, rendererLoader, + (QGstVideoRendererInterface_iid, QLatin1String("video/gstvideorenderer"), Qt::CaseInsensitive)) + +QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface) + : m_surface(surface) + , m_renderer(0) + , m_activeRenderer(0) + , m_surfaceCaps(0) + , m_startCaps(0) + , m_lastBuffer(0) + , m_notified(false) + , m_stop(false) + , m_render(false) + , m_flush(false) +{ + foreach (QObject *instance, rendererLoader()->instances(QGstVideoRendererPluginKey)) { + QGstVideoRendererInterface* plugin = qobject_cast<QGstVideoRendererInterface*>(instance); + if (QGstVideoRenderer *renderer = plugin ? plugin->createRenderer() : 0) + m_renderers.append(renderer); + } + + m_renderers.append(new QGstDefaultVideoRenderer); + updateSupportedFormats(); + connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(updateSupportedFormats())); +} + +QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate() +{ + qDeleteAll(m_renderers); + + if (m_surfaceCaps) + gst_caps_unref(m_surfaceCaps); +} + +GstCaps *QVideoSurfaceGstDelegate::caps() +{ + QMutexLocker locker(&m_mutex); + + gst_caps_ref(m_surfaceCaps); + + return m_surfaceCaps; +} + +bool QVideoSurfaceGstDelegate::start(GstCaps *caps) +{ + QMutexLocker locker(&m_mutex); + + if (m_activeRenderer) { + m_flush = true; + m_stop = true; + } + + m_render = false; + + if (m_lastBuffer) { + gst_buffer_unref(m_lastBuffer); + m_lastBuffer = 0; + } + + if (m_startCaps) + gst_caps_unref(m_startCaps); + m_startCaps = caps; + gst_caps_ref(m_startCaps); + + /* + Waiting for start() to be invoked in the main thread may block + if gstreamer blocks the main thread until this call is finished. + This situation is rare and usually caused by setState(Null) + while pipeline is being prerolled. + + The proper solution to this involves controlling gstreamer pipeline from + other thread than video surface. + + Currently start() fails if wait() timed out. + */ + if (!waitForAsyncEvent(&locker, &m_setupCondition, 1000) && m_startCaps) { + qWarning() << "Failed to start video surface due to main thread blocked."; + gst_caps_unref(m_startCaps); + m_startCaps = 0; + } + + return m_activeRenderer != 0; +} + +void QVideoSurfaceGstDelegate::stop() +{ + QMutexLocker locker(&m_mutex); + + if (!m_activeRenderer) + return; + + m_flush = true; + m_stop = true; + + if (m_startCaps) { + gst_caps_unref(m_startCaps); + m_startCaps = 0; + } + + if (m_lastBuffer) { + gst_buffer_unref(m_lastBuffer); + m_lastBuffer = 0; + } + + waitForAsyncEvent(&locker, &m_setupCondition, 500); +} + +bool QVideoSurfaceGstDelegate::proposeAllocation(GstQuery *query) +{ + QMutexLocker locker(&m_mutex); + + if (QGstVideoRenderer *pool = m_activeRenderer) { + locker.unlock(); + + return pool->proposeAllocation(query); + } else { + return false; + } +} + +void QVideoSurfaceGstDelegate::flush() +{ + QMutexLocker locker(&m_mutex); + + m_flush = true; + m_render = false; + + if (m_lastBuffer) { + gst_buffer_unref(m_lastBuffer); + m_lastBuffer = 0; + } + + notify(); +} + +GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer, bool show) +{ + QMutexLocker locker(&m_mutex); + + if (m_lastBuffer) + gst_buffer_unref(m_lastBuffer); + m_lastBuffer = buffer; + gst_buffer_ref(m_lastBuffer); + + if (show) { + m_render = true; + + return waitForAsyncEvent(&locker, &m_renderCondition, 300) + ? m_renderReturn + : GST_FLOW_ERROR; + } else { + return GST_FLOW_OK; + } +} + +void QVideoSurfaceGstDelegate::handleShowPrerollChange(GObject *object, GParamSpec *, gpointer d) +{ + QVideoSurfaceGstDelegate * const delegate = static_cast<QVideoSurfaceGstDelegate *>(d); + + gboolean showPreroll = true; // "show-preroll-frame" property is true by default + g_object_get(object, "show-preroll-frame", &showPreroll, NULL); + + GstState state = GST_STATE_NULL; + GstState pendingState = GST_STATE_NULL; + gst_element_get_state(GST_ELEMENT(object), &state, &pendingState, 0); + + const bool paused + = (pendingState == GST_STATE_VOID_PENDING && state == GST_STATE_PAUSED) + || pendingState == GST_STATE_PAUSED; + + if (paused) { + QMutexLocker locker(&delegate->m_mutex); + + if (!showPreroll && delegate->m_lastBuffer) { + delegate->m_render = false; + delegate->m_flush = true; + delegate->notify(); + } else if (delegate->m_lastBuffer) { + delegate->m_render = true; + delegate->notify(); + } + } +} + +bool QVideoSurfaceGstDelegate::event(QEvent *event) +{ + if (event->type() == QEvent::UpdateRequest) { + QMutexLocker locker(&m_mutex); + + if (m_notified) { + while (handleEvent(&locker)) {} + m_notified = false; + } + return true; + } else { + return QObject::event(event); + } +} + +bool QVideoSurfaceGstDelegate::handleEvent(QMutexLocker *locker) +{ + if (m_flush) { + m_flush = false; + if (m_activeRenderer) { + locker->unlock(); + + m_activeRenderer->flush(m_surface); + } + } else if (m_stop) { + m_stop = false; + + if (QGstVideoRenderer * const activePool = m_activeRenderer) { + m_activeRenderer = 0; + locker->unlock(); + + activePool->stop(m_surface); + + locker->relock(); + } + } else if (m_startCaps) { + Q_ASSERT(!m_activeRenderer); + + GstCaps * const startCaps = m_startCaps; + m_startCaps = 0; + + if (m_renderer && m_surface) { + locker->unlock(); + + const bool started = m_renderer->start(m_surface, startCaps); + + locker->relock(); + + m_activeRenderer = started + ? m_renderer + : 0; + } else if (QGstVideoRenderer * const activePool = m_activeRenderer) { + m_activeRenderer = 0; + locker->unlock(); + + activePool->stop(m_surface); + + locker->relock(); + } + + gst_caps_unref(startCaps); + } else if (m_render) { + m_render = false; + + if (m_activeRenderer && m_surface && m_lastBuffer) { + GstBuffer *buffer = m_lastBuffer; + gst_buffer_ref(buffer); + + locker->unlock(); + + const bool rendered = m_activeRenderer->present(m_surface, buffer); + + gst_buffer_unref(buffer); + + locker->relock(); + + m_renderReturn = rendered + ? GST_FLOW_OK + : GST_FLOW_ERROR; + + m_renderCondition.wakeAll(); + } else { + m_renderReturn = GST_FLOW_ERROR; + m_renderCondition.wakeAll(); + } + } else { + m_setupCondition.wakeAll(); + + return false; + } + return true; +} + +void QVideoSurfaceGstDelegate::notify() +{ + if (!m_notified) { + m_notified = true; + QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest)); + } +} + +bool QVideoSurfaceGstDelegate::waitForAsyncEvent( + QMutexLocker *locker, QWaitCondition *condition, unsigned long time) +{ + if (QThread::currentThread() == thread()) { + while (handleEvent(locker)) {} + m_notified = false; + + return true; + } else { + notify(); + + return condition->wait(&m_mutex, time); + } +} + +void QVideoSurfaceGstDelegate::updateSupportedFormats() +{ + if (m_surfaceCaps) { + gst_caps_unref(m_surfaceCaps); + m_surfaceCaps = 0; + } + + foreach (QGstVideoRenderer *pool, m_renderers) { + if (GstCaps *caps = pool->getCaps(m_surface)) { + if (gst_caps_is_empty(caps)) { + gst_caps_unref(caps); + continue; + } + + if (m_surfaceCaps) + gst_caps_unref(m_surfaceCaps); + + m_renderer = pool; + m_surfaceCaps = caps; + break; + } else { + gst_caps_unref(caps); + } + } +} + +static GstVideoSinkClass *sink_parent_class; + +#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s)) + +QGstVideoRendererSink *QGstVideoRendererSink::createSink(QAbstractVideoSurface *surface) +{ + QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>( + g_object_new(QGstVideoRendererSink::get_type(), 0)); + + sink->delegate = new QVideoSurfaceGstDelegate(surface); + + g_signal_connect( + G_OBJECT(sink), + "notify::show-preroll-frame", + G_CALLBACK(QVideoSurfaceGstDelegate::handleShowPrerollChange), + sink->delegate); + + return sink; +} + +GType QGstVideoRendererSink::get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = + { + sizeof(QGstVideoRendererSinkClass), // class_size + base_init, // base_init + NULL, // base_finalize + class_init, // class_init + NULL, // class_finalize + NULL, // class_data + sizeof(QGstVideoRendererSink), // instance_size + 0, // n_preallocs + instance_init, // instance_init + 0 // value_table + }; + + type = g_type_register_static( + GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink", &info, GTypeFlags(0)); + } + + return type; +} + +void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data) +{ + Q_UNUSED(class_data); + + sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class)); + + GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class); + base_sink_class->get_caps = QGstVideoRendererSink::get_caps; + base_sink_class->set_caps = QGstVideoRendererSink::set_caps; + base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation; + base_sink_class->preroll = QGstVideoRendererSink::preroll; + base_sink_class->render = QGstVideoRendererSink::render; + + GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class); + element_class->change_state = QGstVideoRendererSink::change_state; + + GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class); + object_class->finalize = QGstVideoRendererSink::finalize; +} + +void QGstVideoRendererSink::base_init(gpointer g_class) +{ + static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE( + "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS( + "video/x-raw, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]")); + + gst_element_class_add_pad_template( + GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template)); +} + +void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class) +{ + VO_SINK(instance); + + Q_UNUSED(g_class); + + sink->delegate = 0; +} + +void QGstVideoRendererSink::finalize(GObject *object) +{ + VO_SINK(object); + + delete sink->delegate; + + // Chain up + G_OBJECT_CLASS(sink_parent_class)->finalize(object); +} + +GstStateChangeReturn QGstVideoRendererSink::change_state( + GstElement *element, GstStateChange transition) +{ + Q_UNUSED(element); + + return GST_ELEMENT_CLASS(sink_parent_class)->change_state( + element, transition); +} + +GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter) +{ + VO_SINK(base); + + GstCaps *caps = sink->delegate->caps(); + GstCaps *unfiltered = caps; + if (filter) { + caps = gst_caps_intersect(unfiltered, filter); + gst_caps_unref(unfiltered); + } + + return caps; +} + +gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *caps) +{ + VO_SINK(base); + +#ifdef DEBUG_VIDEO_SURFACE_SINK + qDebug() << "set_caps:"; + qDebug() << caps; +#endif + + if (!caps) { + sink->delegate->stop(); + + return TRUE; + } else if (sink->delegate->start(caps)) { + return TRUE; + } else { + return FALSE; + } +} + +gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query) +{ + VO_SINK(base); + return sink->delegate->proposeAllocation(query); +} + +GstFlowReturn QGstVideoRendererSink::preroll(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + + gboolean showPreroll = true; // "show-preroll-frame" property is true by default + g_object_get(G_OBJECT(base), "show-preroll-frame", &showPreroll, NULL); + + return sink->delegate->render(buffer, showPreroll); // display frame +} + +GstFlowReturn QGstVideoRendererSink::render(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + return sink->delegate->render(buffer, true); +} + +QT_END_NAMESPACE |