blob: d410be7d94ffce994dec538ee55c28c256d89871 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgstreamervideooverlay_p.h"
#include <QtGui/qguiapplication.h>
#include "qgstutils_p.h"
#if !GST_CHECK_VERSION(1,0,0)
#include <gst/interfaces/xoverlay.h>
#else
#include <gst/video/videooverlay.h>
#endif
#include <QtMultimedia/private/qtmultimediaglobal_p.h>
QT_BEGIN_NAMESPACE
struct ElementMap
{
const char *qtPlatform;
const char *gstreamerElement;
};
// Ordered by descending priority
static const ElementMap elementMap[] =
{
#if QT_CONFIG(gstreamer_gl)
{ "xcb", "glimagesink" },
#endif
{ "xcb", "vaapisink" },
{ "xcb", "xvimagesink" },
{ "xcb", "ximagesink" }
};
class QGstreamerSinkProperties
{
public:
virtual ~QGstreamerSinkProperties()
{
}
virtual bool hasShowPrerollFrame() const = 0;
virtual void reset() = 0;
virtual int brightness() const = 0;
virtual bool setBrightness(int brightness) = 0;
virtual int contrast() const = 0;
virtual bool setContrast(int contrast) = 0;
virtual int hue() const = 0;
virtual bool setHue(int hue) = 0;
virtual int saturation() const = 0;
virtual bool setSaturation(int saturation) = 0;
virtual Qt::AspectRatioMode aspectRatioMode() const = 0;
virtual void setAspectRatioMode(Qt::AspectRatioMode mode) = 0;
};
class QXVImageSinkProperties : public QGstreamerSinkProperties
{
public:
QXVImageSinkProperties(GstElement *sink)
: m_videoSink(sink)
{
m_hasForceAspectRatio = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "force-aspect-ratio");
m_hasBrightness = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness");
m_hasContrast = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast");
m_hasHue = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue");
m_hasSaturation = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation");
m_hasShowPrerollFrame = g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "show-preroll-frame");
}
bool hasShowPrerollFrame() const override
{
return m_hasShowPrerollFrame;
}
void reset() override
{
setAspectRatioMode(m_aspectRatioMode);
setBrightness(m_brightness);
setContrast(m_contrast);
setHue(m_hue);
setSaturation(m_saturation);
}
int brightness() const override
{
int brightness = 0;
if (m_hasBrightness)
g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, nullptr);
return brightness / 10;
}
bool setBrightness(int brightness) override
{
m_brightness = brightness;
if (m_hasBrightness)
g_object_set(G_OBJECT(m_videoSink), "brightness", brightness * 10, nullptr);
return m_hasBrightness;
}
int contrast() const override
{
int contrast = 0;
if (m_hasContrast)
g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, nullptr);
return contrast / 10;
}
bool setContrast(int contrast) override
{
m_contrast = contrast;
if (m_hasContrast)
g_object_set(G_OBJECT(m_videoSink), "contrast", contrast * 10, nullptr);
return m_hasContrast;
}
int hue() const override
{
int hue = 0;
if (m_hasHue)
g_object_get(G_OBJECT(m_videoSink), "hue", &hue, nullptr);
return hue / 10;
}
bool setHue(int hue) override
{
m_hue = hue;
if (m_hasHue)
g_object_set(G_OBJECT(m_videoSink), "hue", hue * 10, nullptr);
return m_hasHue;
}
int saturation() const override
{
int saturation = 0;
if (m_hasSaturation)
g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, nullptr);
return saturation / 10;
}
bool setSaturation(int saturation) override
{
m_saturation = saturation;
if (m_hasSaturation)
g_object_set(G_OBJECT(m_videoSink), "saturation", saturation * 10, nullptr);
return m_hasSaturation;
}
Qt::AspectRatioMode aspectRatioMode() const override
{
Qt::AspectRatioMode mode = Qt::KeepAspectRatio;
if (m_hasForceAspectRatio) {
gboolean forceAR = false;
g_object_get(G_OBJECT(m_videoSink), "force-aspect-ratio", &forceAR, nullptr);
if (!forceAR)
mode = Qt::IgnoreAspectRatio;
}
return mode;
}
void setAspectRatioMode(Qt::AspectRatioMode mode) override
{
m_aspectRatioMode = mode;
if (m_hasForceAspectRatio) {
g_object_set(G_OBJECT(m_videoSink),
"force-aspect-ratio",
(mode == Qt::KeepAspectRatio),
nullptr);
}
}
protected:
GstElement *m_videoSink = nullptr;
bool m_hasForceAspectRatio = false;
bool m_hasBrightness = false;
bool m_hasContrast = false;
bool m_hasHue = false;
bool m_hasSaturation = false;
bool m_hasShowPrerollFrame = false;
Qt::AspectRatioMode m_aspectRatioMode = Qt::KeepAspectRatio;
int m_brightness = 0;
int m_contrast = 0;
int m_hue = 0;
int m_saturation = 0;
};
class QVaapiSinkProperties : public QXVImageSinkProperties
{
public:
QVaapiSinkProperties(GstElement *sink)
: QXVImageSinkProperties(sink)
{
// Set default values.
m_contrast = 1;
m_saturation = 1;
}
int brightness() const override
{
gfloat brightness = 0;
if (m_hasBrightness)
g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, nullptr);
return brightness * 100; // [-1,1] -> [-100,100]
}
bool setBrightness(int brightness) override
{
m_brightness = brightness;
if (m_hasBrightness) {
gfloat v = brightness / 100.0; // [-100,100] -> [-1,1]
g_object_set(G_OBJECT(m_videoSink), "brightness", v, nullptr);
}
return m_hasBrightness;
}
int contrast() const override
{
gfloat contrast = 1;
if (m_hasContrast)
g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, nullptr);
return (contrast - 1) * 100; // [0,2] -> [-100,100]
}
bool setContrast(int contrast) override
{
m_contrast = contrast;
if (m_hasContrast) {
gfloat v = (contrast / 100.0) + 1; // [-100,100] -> [0,2]
g_object_set(G_OBJECT(m_videoSink), "contrast", v, nullptr);
}
return m_hasContrast;
}
int hue() const override
{
gfloat hue = 0;
if (m_hasHue)
g_object_get(G_OBJECT(m_videoSink), "hue", &hue, nullptr);
return hue / 180 * 100; // [-180,180] -> [-100,100]
}
bool setHue(int hue) override
{
m_hue = hue;
if (m_hasHue) {
gfloat v = hue / 100.0 * 180; // [-100,100] -> [-180,180]
g_object_set(G_OBJECT(m_videoSink), "hue", v, nullptr);
}
return m_hasHue;
}
int saturation() const override
{
gfloat saturation = 1;
if (m_hasSaturation)
g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, nullptr);
return (saturation - 1) * 100; // [0,2] -> [-100,100]
}
bool setSaturation(int saturation) override
{
m_saturation = saturation;
if (m_hasSaturation) {
gfloat v = (saturation / 100.0) + 1; // [-100,100] -> [0,2]
g_object_set(G_OBJECT(m_videoSink), "saturation", v, nullptr);
}
return m_hasSaturation;
}
};
static bool qt_gst_element_is_functioning(GstElement *element)
{
GstStateChangeReturn ret = gst_element_set_state(element, GST_STATE_READY);
if (ret == GST_STATE_CHANGE_SUCCESS) {
gst_element_set_state(element, GST_STATE_NULL);
return true;
}
return false;
}
static GstElement *findBestVideoSink()
{
GstElement *choice = 0;
QString platform = QGuiApplication::platformName();
// We need a native window ID to use the GstVideoOverlay interface.
// Bail out if the Qt platform plugin in use cannot provide a sensible WId.
if (platform != QLatin1String("xcb"))
return 0;
// First, try some known video sinks, depending on the Qt platform plugin in use.
for (quint32 i = 0; i < (sizeof(elementMap) / sizeof(ElementMap)); ++i) {
#if QT_CONFIG(gstreamer_gl)
if (!QGstUtils::useOpenGL() && qstrcmp(elementMap[i].gstreamerElement, "glimagesink") == 0)
continue;
#endif
if (platform == QLatin1String(elementMap[i].qtPlatform)
&& (choice = gst_element_factory_make(elementMap[i].gstreamerElement, nullptr))) {
if (qt_gst_element_is_functioning(choice))
return choice;
gst_object_unref(choice);
choice = 0;
}
}
// If none of the known video sinks are available, try to find one that implements the
// GstVideoOverlay interface and has autoplugging rank.
GList *list = qt_gst_video_sinks();
for (GList *item = list; item != nullptr; item = item->next) {
GstElementFactory *f = GST_ELEMENT_FACTORY(item->data);
if (!gst_element_factory_has_interface(f, QT_GSTREAMER_VIDEOOVERLAY_INTERFACE_NAME))
continue;
if (GstElement *el = gst_element_factory_create(f, nullptr)) {
if (qt_gst_element_is_functioning(el)) {
choice = el;
break;
}
gst_object_unref(el);
}
}
gst_plugin_feature_list_free(list);
return choice;
}
QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent, const QByteArray &elementName)
: QObject(parent)
, QGstreamerBufferProbe(QGstreamerBufferProbe::ProbeCaps)
{
GstElement *sink = nullptr;
if (!elementName.isEmpty())
sink = gst_element_factory_make(elementName.constData(), nullptr);
else
sink = findBestVideoSink();
setVideoSink(sink);
}
QGstreamerVideoOverlay::~QGstreamerVideoOverlay()
{
if (m_videoSink) {
delete m_sinkProperties;
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
removeProbeFromPad(pad);
gst_object_unref(GST_OBJECT(pad));
gst_object_unref(GST_OBJECT(m_videoSink));
}
}
GstElement *QGstreamerVideoOverlay::videoSink() const
{
return m_videoSink;
}
void QGstreamerVideoOverlay::setVideoSink(GstElement *sink)
{
if (!sink)
return;
if (m_videoSink)
gst_object_unref(GST_OBJECT(m_videoSink));
m_videoSink = sink;
qt_gst_object_ref_sink(GST_OBJECT(m_videoSink));
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
addProbeToPad(pad);
gst_object_unref(GST_OBJECT(pad));
QString sinkName(QLatin1String(GST_OBJECT_NAME(sink)));
bool isVaapi = sinkName.startsWith(QLatin1String("vaapisink"));
delete m_sinkProperties;
m_sinkProperties = isVaapi ? new QVaapiSinkProperties(sink) : new QXVImageSinkProperties(sink);
if (m_sinkProperties->hasShowPrerollFrame())
g_signal_connect(m_videoSink, "notify::show-preroll-frame",
G_CALLBACK(showPrerollFrameChanged), this);
}
QSize QGstreamerVideoOverlay::nativeVideoSize() const
{
return m_nativeVideoSize;
}
void QGstreamerVideoOverlay::setWindowHandle(WId id)
{
m_windowId = id;
if (isActive())
setWindowHandle_helper(id);
}
void QGstreamerVideoOverlay::setWindowHandle_helper(WId id)
{
#if GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), id);
#else
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
# if GST_CHECK_VERSION(0,10,31)
gst_x_overlay_set_window_handle(GST_X_OVERLAY(m_videoSink), id);
# else
gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), id);
# endif
#endif
// Properties need to be reset when changing the winId.
m_sinkProperties->reset();
}
}
void QGstreamerVideoOverlay::expose()
{
if (!isActive())
return;
#if !GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink))
gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink));
#else
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY(m_videoSink));
}
#endif
}
void QGstreamerVideoOverlay::setRenderRectangle(const QRect &rect)
{
int x = -1;
int y = -1;
int w = -1;
int h = -1;
if (!rect.isEmpty()) {
x = rect.x();
y = rect.y();
w = rect.width();
h = rect.height();
}
#if GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink))
gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(m_videoSink), x, y, w, h);
#elif GST_CHECK_VERSION(0, 10, 29)
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink))
gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), x, y , w , h);
#else
Q_UNUSED(x)
Q_UNUSED(y)
Q_UNUSED(w)
Q_UNUSED(h)
#endif
}
bool QGstreamerVideoOverlay::processSyncMessage(const QGstreamerMessage &message)
{
GstMessage* gm = message.rawMessage();
#if !GST_CHECK_VERSION(1,0,0)
if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(gm->structure, "prepare-xwindow-id")) {
#else
if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(gst_message_get_structure(gm), "prepare-window-handle")) {
#endif
setWindowHandle_helper(m_windowId);
return true;
}
return false;
}
bool QGstreamerVideoOverlay::processBusMessage(const QGstreamerMessage &message)
{
GstMessage* gm = message.rawMessage();
if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED &&
GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) {
updateIsActive();
}
return false;
}
void QGstreamerVideoOverlay::probeCaps(GstCaps *caps)
{
QSize size = QGstUtils::capsCorrectedResolution(caps);
if (size != m_nativeVideoSize) {
m_nativeVideoSize = size;
emit nativeVideoSizeChanged();
}
}
bool QGstreamerVideoOverlay::isActive() const
{
return m_isActive;
}
void QGstreamerVideoOverlay::updateIsActive()
{
if (!m_videoSink)
return;
GstState state = GST_STATE(m_videoSink);
gboolean showPreroll = true;
if (m_sinkProperties->hasShowPrerollFrame())
g_object_get(G_OBJECT(m_videoSink), "show-preroll-frame", &showPreroll, nullptr);
bool newIsActive = (state == GST_STATE_PLAYING || (state == GST_STATE_PAUSED && showPreroll));
if (newIsActive != m_isActive) {
m_isActive = newIsActive;
emit activeChanged();
}
}
void QGstreamerVideoOverlay::showPrerollFrameChanged(GObject *, GParamSpec *, QGstreamerVideoOverlay *overlay)
{
overlay->updateIsActive();
}
Qt::AspectRatioMode QGstreamerVideoOverlay::aspectRatioMode() const
{
return m_sinkProperties->aspectRatioMode();
}
void QGstreamerVideoOverlay::setAspectRatioMode(Qt::AspectRatioMode mode)
{
m_sinkProperties->setAspectRatioMode(mode);
}
int QGstreamerVideoOverlay::brightness() const
{
return m_sinkProperties->brightness();
}
void QGstreamerVideoOverlay::setBrightness(int brightness)
{
if (m_sinkProperties->setBrightness(brightness))
emit brightnessChanged(brightness);
}
int QGstreamerVideoOverlay::contrast() const
{
return m_sinkProperties->contrast();
}
void QGstreamerVideoOverlay::setContrast(int contrast)
{
if (m_sinkProperties->setContrast(contrast))
emit contrastChanged(contrast);
}
int QGstreamerVideoOverlay::hue() const
{
return m_sinkProperties->hue();
}
void QGstreamerVideoOverlay::setHue(int hue)
{
if (m_sinkProperties->setHue(hue))
emit hueChanged(hue);
}
int QGstreamerVideoOverlay::saturation() const
{
return m_sinkProperties->saturation();
}
void QGstreamerVideoOverlay::setSaturation(int saturation)
{
if (m_sinkProperties->setSaturation(saturation))
emit saturationChanged(saturation);
}
QT_END_NAMESPACE