diff options
author | George Kiagiadakis <george.kiagiadakis@collabora.com> | 2011-11-01 14:42:43 +0200 |
---|---|---|
committer | George Kiagiadakis <george.kiagiadakis@collabora.com> | 2012-01-06 17:51:15 +0200 |
commit | 50ba34fd0aa175e11c819994a64d38358bffc02c (patch) | |
tree | dca284105ea3d531108c4ba755b3e8c5ec9ee6ba | |
parent | fdcd7ff4302af28de7f8d04b76ca944a01156f33 (diff) |
qtvideosink: Initial refactoring.
* Eliminated QtMultimedia dependencies.
* Eliminated the need to link to the internal classes of the sink
by adding a standard properties/signals interface to the gst element.
* Simplified some parts of the initial code, which were there only to
support plugging the components individually to different parts
of QtMultimedia.
-rw-r--r-- | elements/gstqtvideosink/abstractsurfacepainter.h | 43 | ||||
-rw-r--r-- | elements/gstqtvideosink/bufferformat.cpp | 246 | ||||
-rw-r--r-- | elements/gstqtvideosink/bufferformat.h | 95 | ||||
-rw-r--r-- | elements/gstqtvideosink/genericsurfacepainter.cpp | 68 | ||||
-rw-r--r-- | elements/gstqtvideosink/genericsurfacepainter.h | 50 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosink.cpp | 925 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosink.h | 163 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosinkplugin.cpp | 42 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosinkplugin.h | 25 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosinksurface.cpp | 1842 | ||||
-rw-r--r-- | elements/gstqtvideosink/gstqtvideosinksurface.h | 215 | ||||
-rw-r--r-- | elements/gstqtvideosink/openglsurfacepainter.cpp | 832 | ||||
-rw-r--r-- | elements/gstqtvideosink/openglsurfacepainter.h | 132 |
13 files changed, 2235 insertions, 2443 deletions
diff --git a/elements/gstqtvideosink/abstractsurfacepainter.h b/elements/gstqtvideosink/abstractsurfacepainter.h new file mode 100644 index 0000000..87fcd13 --- /dev/null +++ b/elements/gstqtvideosink/abstractsurfacepainter.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ +#ifndef ABSTRACTSURFACEPAINTER_H +#define ABSTRACTSURFACEPAINTER_H + +#include "bufferformat.h" +#include <QtCore/QSet> + +class QPainter; + +/** Common interface for all the painters */ +class AbstractSurfacePainter +{ +public: + virtual ~AbstractSurfacePainter() {} + + virtual bool supportsFormat(BufferFormat::PixelFormat format) const = 0; + + virtual void init(const BufferFormat & format) = 0; + virtual void cleanup() = 0; + + virtual void paint(quint8 *data, const BufferFormat & frameFormat, + QPainter *painter, const QRect & videoArea, const QRect & clipRect) = 0; + + virtual void updateColors(int brightness, int contrast, int hue, int saturation, + BufferFormat::YCbCrColorSpace colorSpace) = 0; +}; + +#endif diff --git a/elements/gstqtvideosink/bufferformat.cpp b/elements/gstqtvideosink/bufferformat.cpp new file mode 100644 index 0000000..a18dd65 --- /dev/null +++ b/elements/gstqtvideosink/bufferformat.cpp @@ -0,0 +1,246 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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 "bufferformat.h" +#include <gst/gst.h> + +//BEGIN YuvFormat + +struct YuvFormat +{ + BufferFormat::PixelFormat pixelFormat; + guint32 fourcc; + int bitsPerPixel; +}; + +static const YuvFormat qt_yuvColorLookup[] = +{ + { BufferFormat::YUV420P, GST_MAKE_FOURCC('I','4','2','0'), 8 }, + { BufferFormat::YV12, GST_MAKE_FOURCC('Y','V','1','2'), 8 }, + { BufferFormat::UYVY, GST_MAKE_FOURCC('U','Y','V','Y'), 16 }, + { BufferFormat::YUYV, GST_MAKE_FOURCC('Y','U','Y','2'), 16 }, + { BufferFormat::NV12, GST_MAKE_FOURCC('N','V','1','2'), 8 }, + { BufferFormat::NV21, GST_MAKE_FOURCC('N','V','2','1'), 8 }, + { BufferFormat::AYUV444, GST_MAKE_FOURCC('A','Y','U','V'), 32 } +}; + +static int indexOfYuvColor(BufferFormat::PixelFormat format) +{ + const int count = sizeof(qt_yuvColorLookup) / sizeof(YuvFormat); + + for (int i = 0; i < count; ++i) + if (qt_yuvColorLookup[i].pixelFormat == format) + return i; + + return -1; +} + +static int indexOfYuvColor(guint32 fourcc) +{ + const int count = sizeof(qt_yuvColorLookup) / sizeof(YuvFormat); + + for (int i = 0; i < count; ++i) + if (qt_yuvColorLookup[i].fourcc == fourcc) + return i; + + return -1; +} + +//END YuvFormat +//BEGIN RgbFormat + +struct RgbFormat +{ + BufferFormat::PixelFormat pixelFormat; + int bitsPerPixel; + int depth; + int endianness; + int red; + int green; + int blue; + int alpha; +}; + +static const RgbFormat qt_rgbColorLookup[] = +{ + { BufferFormat::RGB32 , 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000 }, + { BufferFormat::RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, + { BufferFormat::BGR32 , 32, 24, 4321, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000 }, + { BufferFormat::BGR32 , 32, 24, 1234, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, + { BufferFormat::ARGB32, 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF }, + { BufferFormat::ARGB32, 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 }, + { BufferFormat::RGB24 , 24, 24, 4321, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, + { BufferFormat::BGR24 , 24, 24, 4321, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, + { BufferFormat::RGB565, 16, 16, 1234, 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 } +}; + +static int indexOfRgbColor( + int bits, int depth, int endianness, int red, int green, int blue, int alpha) +{ + const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); + + for (int i = 0; i < count; ++i) { + if (qt_rgbColorLookup[i].bitsPerPixel == bits + && qt_rgbColorLookup[i].depth == depth + && qt_rgbColorLookup[i].endianness == endianness + && qt_rgbColorLookup[i].red == red + && qt_rgbColorLookup[i].green == green + && qt_rgbColorLookup[i].blue == blue + && qt_rgbColorLookup[i].alpha == alpha) { + return i; + } + } + return -1; +} + +//END RgbFormat +//BEGIN BufferFormat + +QImage::Format BufferFormat::pixelFormatToImageFormat(PixelFormat format) +{ + switch (format) { + case ARGB32: + return QImage::Format_ARGB32; + case RGB32: + return QImage::Format_RGB32; + case RGB24: + return QImage::Format_RGB888; + case RGB565: + return QImage::Format_RGB16; + case RGB555: + return QImage::Format_RGB555; + case BGRA32: + case BGR32: + case BGR24: + case BGR565: + case BGR555: + case AYUV444: + case YUV444: + case YUV420P: + case YV12: + case UYVY: + case YUYV: + case NV12: + case NV21: + case Invalid: + return QImage::Format_Invalid; + } + return QImage::Format_Invalid; +} + +GstCaps *BufferFormat::pixelFormatToCaps(PixelFormat format) +{ + GstCaps *caps = gst_caps_new_empty(); + + if (int index = indexOfYuvColor(format) != -1) + { + GstStructure *structure = gst_structure_new( + "video/x-raw-yuv", + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, + "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "format" , GST_TYPE_FOURCC, qt_yuvColorLookup[index].fourcc, + NULL); + gst_caps_append_structure(caps, structure); + } + else + { + const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); + + for (int i = 0; i < count; ++i) { + if (qt_rgbColorLookup[i].pixelFormat == format) { + GstStructure *structure = gst_structure_new( + "video/x-raw-rgb", + "framerate" , GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, + "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "bpp" , G_TYPE_INT, qt_rgbColorLookup[i].bitsPerPixel, + "depth" , G_TYPE_INT, qt_rgbColorLookup[i].depth, + "endianness", G_TYPE_INT, qt_rgbColorLookup[i].endianness, + "red_mask" , G_TYPE_INT, qt_rgbColorLookup[i].red, + "green_mask", G_TYPE_INT, qt_rgbColorLookup[i].green, + "blue_mask" , G_TYPE_INT, qt_rgbColorLookup[i].blue, + NULL); + + if (qt_rgbColorLookup[i].alpha != 0) { + gst_structure_set( + structure, "alpha_mask", G_TYPE_INT, qt_rgbColorLookup[i].alpha, NULL); + } + gst_caps_append_structure(caps, structure); + } + } + } + + return caps; +} + +BufferFormat BufferFormat::fromCaps(const GstCaps *caps) +{ + BufferFormat result; + if (caps) { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + int bitsPerPixel = 0; + + if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-yuv") == 0) { + guint32 fourcc = 0; + gst_structure_get_fourcc(structure, "format", &fourcc); + + int index = indexOfYuvColor(fourcc); + if (index != -1) { + result.d->pixelFormat = qt_yuvColorLookup[index].pixelFormat; + bitsPerPixel = qt_yuvColorLookup[index].bitsPerPixel; + } + } else if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) { + int depth = 0; + int endianness = 0; + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + + gst_structure_get_int(structure, "bpp", &bitsPerPixel); + gst_structure_get_int(structure, "depth", &depth); + gst_structure_get_int(structure, "endianness", &endianness); + gst_structure_get_int(structure, "red_mask", &red); + gst_structure_get_int(structure, "green_mask", &green); + gst_structure_get_int(structure, "blue_mask", &blue); + gst_structure_get_int(structure, "alpha_mask", &alpha); + + int index = indexOfRgbColor(bitsPerPixel, depth, endianness, red, green, blue, alpha); + + if (index != -1) { + result.d->pixelFormat = qt_rgbColorLookup[index].pixelFormat; + } + } + + if (result.d->pixelFormat != BufferFormat::Invalid) { + gst_structure_get_int(structure, "width", &result.d->frameSize.rwidth()); + gst_structure_get_int(structure, "height", &result.d->frameSize.rheight()); + gst_structure_get_fraction(structure, "pixel-aspect-ratio", + &result.d->pixelAspectRatio.rwidth(), + &result.d->pixelAspectRatio.rheight()); + + result.d->bytesPerLine = ((result.d->frameSize.width() * bitsPerPixel / 8) + 3) & ~3; + + //FIXME not sure if this should be read from caps somehow + //The original QtMM code always uses this colorspace + result.d->colorSpace = BufferFormat::YCbCr_BT601; + } + } + return result; +} + +//END BufferFormat diff --git a/elements/gstqtvideosink/bufferformat.h b/elements/gstqtvideosink/bufferformat.h new file mode 100644 index 0000000..2c30f84 --- /dev/null +++ b/elements/gstqtvideosink/bufferformat.h @@ -0,0 +1,95 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ +#ifndef BUFFERFORMAT_H +#define BUFFERFORMAT_H + +#include <QtCore/QSharedData> +#include <QtCore/QSize> +#include <QtGui/QImage> + +typedef struct _GstCaps GstCaps; + + +/** + * This class is a cheap way to represent Caps. + * Based on QVideoSurfaceFormat. + */ +class BufferFormat +{ +public: + enum PixelFormat { + Invalid, + ARGB32, + RGB32, + RGB24, + RGB565, + RGB555, /* unused */ + BGRA32, /* unused */ + BGR32, + BGR24, + BGR565, /* unused */ + BGR555, /* unused */ + AYUV444, + YUV444, /* unused */ + YUV420P, + YV12, + UYVY, + YUYV, + NV12, + NV21 + }; + + enum YCbCrColorSpace { + YCbCr_Undefined, + YCbCr_BT601, + YCbCr_BT709, + YCbCr_xvYCC601, + YCbCr_xvYCC709, + YCbCr_JPEG + }; + + static QImage::Format pixelFormatToImageFormat(PixelFormat format); + static GstCaps *pixelFormatToCaps(PixelFormat format); + + static BufferFormat fromCaps(const GstCaps *caps); + inline BufferFormat() : d(new Data) {} + + inline PixelFormat pixelFormat() const { return d->pixelFormat; } + inline YCbCrColorSpace yCbCrColorSpace() const { return d->colorSpace; } + inline QSize frameSize() const { return d->frameSize; } + inline QSize pixelAspectRatio() const { return d->pixelAspectRatio; } + inline int bytesPerLine() const { return d->bytesPerLine; } + +private: + struct Data : public QSharedData + { + Data() : + pixelFormat(Invalid), + colorSpace(BufferFormat::YCbCr_Undefined), + bytesPerLine(0) + {} + + PixelFormat pixelFormat; + YCbCrColorSpace colorSpace; + QSize frameSize; + QSize pixelAspectRatio; + int bytesPerLine; + }; + QSharedDataPointer<Data> d; +}; + +#endif // BUFFERFORMAT_H diff --git a/elements/gstqtvideosink/genericsurfacepainter.cpp b/elements/gstqtvideosink/genericsurfacepainter.cpp new file mode 100644 index 0000000..fb45978 --- /dev/null +++ b/elements/gstqtvideosink/genericsurfacepainter.cpp @@ -0,0 +1,68 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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 "genericsurfacepainter.h" +#include <gst/gst.h> +#include <QtGui/QPainter> + +GenericSurfacePainter::GenericSurfacePainter() + : m_imageFormat(QImage::Format_Invalid) +{ +} + +//static +QSet<BufferFormat::PixelFormat> GenericSurfacePainter::supportedPixelFormats() +{ + return QSet<BufferFormat::PixelFormat>() + << BufferFormat::RGB32 +#ifndef QT_OPENGL_ES // The raster formats should be a subset of the GL formats. + << BufferFormat::RGB24 +#endif + << BufferFormat::ARGB32 + << BufferFormat::RGB565; +} + +void GenericSurfacePainter::init(const BufferFormat &format) +{ + m_imageFormat = BufferFormat::pixelFormatToImageFormat(format.pixelFormat()); +} + +void GenericSurfacePainter::cleanup() +{ + m_imageFormat = QImage::Format_Invalid; +} + +void GenericSurfacePainter::paint(quint8 *data, + const BufferFormat & frameFormat, + QPainter *painter, + const QRect & videoArea, + const QRect & clipRect) +{ + Q_ASSERT(m_imageFormat != QImage::Format_Invalid); + + QImage image( + data, + frameFormat.frameSize().width(), + frameFormat.frameSize().height(), + frameFormat.bytesPerLine(), + m_imageFormat); + + painter->drawImage(videoArea, image, clipRect); +} + +void GenericSurfacePainter::updateColors(int, int, int, int, BufferFormat::YCbCrColorSpace) +{ +} diff --git a/elements/gstqtvideosink/genericsurfacepainter.h b/elements/gstqtvideosink/genericsurfacepainter.h new file mode 100644 index 0000000..4ca037c --- /dev/null +++ b/elements/gstqtvideosink/genericsurfacepainter.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ +#ifndef GENERICSURFACEPAINTER_H +#define GENERICSURFACEPAINTER_H + +#include "abstractsurfacepainter.h" + +/** + * Generic painter that paints using the QPainter API. + * No colorspace conversion is done and no colors adjustment either. + */ +class GenericSurfacePainter : public AbstractSurfacePainter +{ +public: + GenericSurfacePainter(); + + static QSet<BufferFormat::PixelFormat> supportedPixelFormats(); + + virtual bool supportsFormat(BufferFormat::PixelFormat format) const { + return supportedPixelFormats().contains(format); + } + + virtual void init(const BufferFormat &format); + virtual void cleanup(); + + virtual void paint(quint8 *data, const BufferFormat & frameFormat, + QPainter *painter, const QRect & videoArea, const QRect & clipRect); + + virtual void updateColors(int brightness, int contrast, int hue, int saturation, + BufferFormat::YCbCrColorSpace colorSpace); + +private: + QImage::Format m_imageFormat; +}; + +#endif // GENERICSURFACEPAINTER_H diff --git a/elements/gstqtvideosink/gstqtvideosink.cpp b/elements/gstqtvideosink/gstqtvideosink.cpp index e42293f..3ffc2f1 100644 --- a/elements/gstqtvideosink/gstqtvideosink.cpp +++ b/elements/gstqtvideosink/gstqtvideosink.cpp @@ -1,749 +1,306 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Collabora Ltd -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file was originally part of the Qt Mobility Components. -** -** 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. -** -****************************************************************************/ - -#include <qabstractvideosurface.h> -#include <qmlpaintervideosurface.h> -#include <qvideoframe.h> -#include <QDebug> -#include <QMap> -#include <QDebug> -#include <QThread> - -#include "qmlgstvideobuffer.h" - -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) -#include <QtGui/qx11info_x11.h> -#include "qmlgstxvimagebuffer.h" -#endif - -#include "qmlvideosurfacegstsink.h" - -//#define DEBUG_VIDEO_SURFACE_SINK - - -Q_DECLARE_METATYPE(QVideoSurfaceFormat) - -QmlVideoSurfaceGstDelegate::QmlVideoSurfaceGstDelegate(QAbstractVideoSurface *surface) - : m_surface(surface) - , m_renderReturn(GST_FLOW_ERROR) - , m_bytesPerLine(0) -{ - if (m_surface) { - m_supportedPixelFormats = m_surface->supportedPixelFormats(); - m_supportedXVideoPixelFormats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::XvShmImageHandle); - connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged())); - } -} - -QList<QVideoFrame::PixelFormat> QmlVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const -{ - QMutexLocker locker(const_cast<QMutex *>(&m_mutex)); - - if (!m_surface) - return QList<QVideoFrame::PixelFormat>(); - else if (handleType == QAbstractVideoBuffer::NoHandle) - return m_supportedPixelFormats; - else if (handleType == QAbstractVideoBuffer::XvShmImageHandle) - return m_supportedXVideoPixelFormats; - else - return m_surface->supportedPixelFormats(handleType); -} - -QVideoSurfaceFormat QmlVideoSurfaceGstDelegate::surfaceFormat() const -{ - QMutexLocker locker(const_cast<QMutex *>(&m_mutex)); - return m_format; -} - -bool QmlVideoSurfaceGstDelegate::start(const QVideoSurfaceFormat &format, int bytesPerLine) -{ - if (!m_surface) - return false; - - QMutexLocker locker(&m_mutex); - - m_format = format; - m_bytesPerLine = bytesPerLine; - - if (QThread::currentThread() == thread()) { - m_started = !m_surface.isNull() ? m_surface->start(m_format) : false; - } else { - QMetaObject::invokeMethod(this, "queuedStart", Qt::QueuedConnection); - - m_setupCondition.wait(&m_mutex); +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ + +/** + * SECTION:element-qtvideosink + * + * qtvideosink is a video sink element that can draw directly on a Qt + * surface, such as QWidget or QGraphicsItem, using either the QPainter API + * or the OpenGL/OpenGLES API. + */ + +#include "gstqtvideosink.h" +#include "gstqtvideosinksurface.h" +#include "gstqtvideosinkmarshal.h" + +#include <QtCore/QCoreApplication> + +guint GstQtVideoSink::s_signals[]; +GstVideoSinkClass *GstQtVideoSink::s_parent_class = NULL; + +GType GstQtVideoSink::get_type() +{ + /* The typedef for GType may be gulong or gsize, depending on the + * system and whether the compiler is c++ or not. The g_once_init_* + * functions always take a gsize * though ... */ + static volatile gsize gonce_data = 0; + if (g_once_init_enter(&gonce_data)) { + GType type; + type = gst_type_register_static_full( + GST_TYPE_VIDEO_SINK, + g_intern_static_string("GstQtVideoSink"), + sizeof(GstQtVideoSinkClass), + &GstQtVideoSink::base_init, + NULL, /* base_finalize */ + &GstQtVideoSink::class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof(GstQtVideoSink), + 0, /* n_preallocs */ + &GstQtVideoSink::init, + NULL, + (GTypeFlags) 0); + g_once_init_leave(&gonce_data, (gsize) type); } - - m_format = m_surface->surfaceFormat(); - - return m_started; + return (GType) gonce_data; } -void QmlVideoSurfaceGstDelegate::stop() +void GstQtVideoSink::base_init(gpointer g_class) { - if (!m_surface) - return; + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); - QMutexLocker locker(&m_mutex); + static GstStaticPadTemplate sink_pad_template = + GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + GST_STATIC_CAPS( + "video/x-raw-rgb, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]" + "; " + "video/x-raw-yuv, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]" + "; " + ) + ); - if (QThread::currentThread() == thread()) { - if (!m_surface.isNull()) - m_surface->stop(); - } else { - QMetaObject::invokeMethod(this, "queuedStop", Qt::QueuedConnection); + gst_element_class_set_details_simple(element_class, "Qt video sink", "Sink/Video", + "A video sink that can draw on any Qt surface (QPaintDevice/QWidget/QGraphicsItem/QML)", + "George Kiagiadakis <george.kiagiadakis@collabora.com>"); - m_setupCondition.wait(&m_mutex); - } - - m_started = false; -} - -bool QmlVideoSurfaceGstDelegate::isActive() -{ - QMutexLocker locker(&m_mutex); - return !m_surface.isNull() && m_surface->isActive(); -} - -void QmlVideoSurfaceGstDelegate::setPlaying(bool playing) -{ - if (!m_surface.isNull()) { - QmlPainterVideoSurface *surface = qobject_cast<QmlPainterVideoSurface*>(m_surface.data()); - if (surface) - surface->setPlaying(playing); - } + gst_element_class_add_pad_template( + element_class, gst_static_pad_template_get(&sink_pad_template)); } -GstFlowReturn QmlVideoSurfaceGstDelegate::render(GstBuffer *buffer) +void GstQtVideoSink::class_init(gpointer g_class, gpointer class_data) { - if (!m_surface) { - qWarning() << "Rendering video frame to deleted surface, skip."; - //return GST_FLOW_NOT_NEGOTIATED; - return GST_FLOW_OK; - } - - QMutexLocker locker(&m_mutex); - - QmlGstVideoBuffer *videoBuffer = 0; + Q_UNUSED(class_data); -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) - if (G_TYPE_CHECK_INSTANCE_TYPE(buffer, QmlGstXvImageBuffer::get_type())) { - QmlGstXvImageBuffer *xvBuffer = reinterpret_cast<QmlGstXvImageBuffer *>(buffer); - QVariant handle = QVariant::fromValue(xvBuffer->xvImage); - videoBuffer = new QmlGstVideoBuffer(buffer, m_bytesPerLine, QAbstractVideoBuffer::XvShmImageHandle, handle); - } else + s_parent_class = reinterpret_cast<GstVideoSinkClass*>(g_type_class_peek_parent(g_class)); + + GObjectClass *object_class = G_OBJECT_CLASS(g_class); + object_class->finalize = GstQtVideoSink::finalize; + object_class->set_property = GstQtVideoSink::set_property; + object_class->get_property = GstQtVideoSink::get_property; + + GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); + element_class->change_state = GstQtVideoSink::change_state; + + GstBaseSinkClass *base_sink_class = GST_BASE_SINK_CLASS(g_class); + base_sink_class->get_caps = GstQtVideoSink::get_caps; + base_sink_class->set_caps = GstQtVideoSink::set_caps; + base_sink_class->buffer_alloc = GstQtVideoSink::buffer_alloc; + + GstVideoSinkClass *video_sink_class = GST_VIDEO_SINK_CLASS(g_class); + video_sink_class->show_frame = GstQtVideoSink::show_frame; + + GstQtVideoSinkClass *qt_video_sink_class = GST_QT_VIDEO_SINK_CLASS(g_class); + qt_video_sink_class->paint = GstQtVideoSink::paint; + + + /** + * GstQtVideoSink::force-aspect-ratio + * + * If set to TRUE, the sink will scale the video respecting its original aspect ratio + * and any remaining space will be filled with black. + * If set to FALSE, the sink will scale the video to fit the whole drawing area. + **/ + g_object_class_install_property(object_class, PROP_FORCE_ASPECT_RATIO, + g_param_spec_boolean("force-aspect-ratio", "Force aspect ratio", + "When enabled, scaling will respect original aspect ratio", + FALSE, static_cast<GParamFlags>(G_PARAM_READWRITE))); + +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + /** + * GstQtVideoSink::glcontext + * + * This property holds a pointer to the QGLContext that will be used to render + * the video using OpenGL acceleration. You must set this to a valid QGLContext + * pointer before linking this element, or else OpenGL acceleration will be disabled. + **/ + g_object_class_install_property(object_class, PROP_GLCONTEXT, + g_param_spec_pointer("glcontext", "GL context", + "The QGLContext that will be used to do OpenGL-accelerated rendering", + static_cast<GParamFlags>(G_PARAM_READWRITE))); #endif - videoBuffer = new QmlGstVideoBuffer(buffer, m_bytesPerLine); - - m_frame = QVideoFrame( - videoBuffer, - m_format.frameSize(), - m_format.pixelFormat()); - - qint64 startTime = GST_BUFFER_TIMESTAMP(buffer); - if (startTime >= 0) { - m_frame.setStartTime(startTime/G_GINT64_CONSTANT (1000000)); - - qint64 duration = GST_BUFFER_DURATION(buffer); - - if (duration >= 0) - m_frame.setEndTime((startTime + duration)/G_GINT64_CONSTANT (1000000)); - } - - QMetaObject::invokeMethod(this, "queuedRender", Qt::QueuedConnection); - - if (!m_renderCondition.wait(&m_mutex, 300)) { - m_frame = QVideoFrame(); - - return GST_FLOW_OK; - } else { - return m_renderReturn; - } -} - -void QmlVideoSurfaceGstDelegate::queuedStart() -{ - QMutexLocker locker(&m_mutex); - - m_started = m_surface->start(m_format); - - m_setupCondition.wakeAll(); -} - -void QmlVideoSurfaceGstDelegate::queuedStop() -{ - QMutexLocker locker(&m_mutex); - - m_surface->stop(); - - m_setupCondition.wakeAll(); -} - -void QmlVideoSurfaceGstDelegate::queuedRender() -{ - QMutexLocker locker(&m_mutex); - - if (m_surface.isNull()) { - qWarning() << "Rendering video frame to deleted surface, skip the frame"; - m_renderReturn = GST_FLOW_OK; - } else if (m_surface->present(m_frame)) { - m_renderReturn = GST_FLOW_OK; - } else { - switch (m_surface->error()) { - case QAbstractVideoSurface::NoError: - m_renderReturn = GST_FLOW_OK; - break; - case QAbstractVideoSurface::StoppedError: - //It's likely we are in process of changing video output - //and the surface is already stopped, ignore the frame - m_renderReturn = GST_FLOW_OK; - break; - default: - qWarning() << "Failed to render video frame:" << m_surface->error(); - m_renderReturn = GST_FLOW_OK; - break; - } - } - - m_renderCondition.wakeAll(); -} - -void QmlVideoSurfaceGstDelegate::supportedFormatsChanged() -{ - QMutexLocker locker(&m_mutex); - - m_supportedPixelFormats.clear(); - m_supportedXVideoPixelFormats.clear(); - if (m_surface) { - m_supportedPixelFormats = m_surface->supportedPixelFormats(); - m_supportedXVideoPixelFormats = m_surface->supportedPixelFormats(QAbstractVideoBuffer::XvShmImageHandle); - } -} - -struct YuvFormat -{ - QVideoFrame::PixelFormat pixelFormat; - guint32 fourcc; - int bitsPerPixel; -}; - -static const YuvFormat qt_yuvColorLookup[] = -{ - { QVideoFrame::Format_YUV420P, GST_MAKE_FOURCC('I','4','2','0'), 8 }, - { QVideoFrame::Format_YV12, GST_MAKE_FOURCC('Y','V','1','2'), 8 }, - { QVideoFrame::Format_UYVY, GST_MAKE_FOURCC('U','Y','V','Y'), 16 }, - { QVideoFrame::Format_YUYV, GST_MAKE_FOURCC('Y','U','Y','2'), 16 }, - { QVideoFrame::Format_NV12, GST_MAKE_FOURCC('N','V','1','2'), 8 }, - { QVideoFrame::Format_NV21, GST_MAKE_FOURCC('N','V','2','1'), 8 }, - { QVideoFrame::Format_AYUV444, GST_MAKE_FOURCC('A','Y','U','V'), 32 } -}; - -static int indexOfYuvColor(QVideoFrame::PixelFormat format) -{ - const int count = sizeof(qt_yuvColorLookup) / sizeof(YuvFormat); - - for (int i = 0; i < count; ++i) - if (qt_yuvColorLookup[i].pixelFormat == format) - return i; + /** + * GstQtVideoSink::paint + * @painter: A valid QPainter pointer that will be used to paint the video + * @x: The x coordinate of the target area rectangle + * @y: The y coordinate of the target area rectangle + * @width: The width of the target area rectangle + * @height: The height of the target area rectangle + * + * This is an action signal that you can call from your Qt surface class inside + * its paint function to render the video. It takes a QPainter* and the target + * area rectangle as arguments. You should schedule to call this function to + * repaint the surface whenever the ::update signal is emited. + */ + s_signals[PAINT_SIGNAL] = + g_signal_new("paint", G_TYPE_FROM_CLASS(g_class), + static_cast<GSignalFlags>(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET(GstQtVideoSinkClass, paint), + NULL, NULL, + g_cclosure_user_marshal_VOID__POINTER_INT_INT_INT_INT, + G_TYPE_NONE, 5, + G_TYPE_POINTER, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT, G_TYPE_INT); + + /** + * GstQtVideoSink::update + * + * This signal is emited when the surface should be repainted. It should + * be connected to QWidget::update() or QGraphicsItem::update() or any + * other similar function in your surface. + */ + s_signals[UPDATE_SIGNAL] = + g_signal_new("update", G_TYPE_FROM_CLASS(g_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + +void GstQtVideoSink::init(GTypeInstance *instance, gpointer g_class) +{ + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(instance); + Q_UNUSED(g_class); - return -1; + sink->surface = new GstQtVideoSinkSurface(sink); + sink->formatDirty = true; } -static int indexOfYuvColor(guint32 fourcc) +void GstQtVideoSink::finalize(GObject *object) { - const int count = sizeof(qt_yuvColorLookup) / sizeof(YuvFormat); - - for (int i = 0; i < count; ++i) - if (qt_yuvColorLookup[i].fourcc == fourcc) - return i; + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(object); - return -1; + delete sink->surface; + sink->surface = 0; } -struct RgbFormat -{ - QVideoFrame::PixelFormat pixelFormat; - int bitsPerPixel; - int depth; - int endianness; - int red; - int green; - int blue; - int alpha; -}; - -static const RgbFormat qt_rgbColorLookup[] = +void GstQtVideoSink::set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) { - { QVideoFrame::Format_RGB32 , 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000 }, - { QVideoFrame::Format_RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, - { QVideoFrame::Format_BGR32 , 32, 24, 4321, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000 }, - { QVideoFrame::Format_BGR32 , 32, 24, 1234, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, - { QVideoFrame::Format_ARGB32, 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF }, - { QVideoFrame::Format_ARGB32, 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 }, - { QVideoFrame::Format_RGB24 , 24, 24, 4321, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, - { QVideoFrame::Format_BGR24 , 24, 24, 4321, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, - { QVideoFrame::Format_RGB565, 16, 16, 1234, 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 } -}; - -static int indexOfRgbColor( - int bits, int depth, int endianness, int red, int green, int blue, int alpha) -{ - const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); - - for (int i = 0; i < count; ++i) { - if (qt_rgbColorLookup[i].bitsPerPixel == bits - && qt_rgbColorLookup[i].depth == depth - && qt_rgbColorLookup[i].endianness == endianness - && qt_rgbColorLookup[i].red == red - && qt_rgbColorLookup[i].green == green - && qt_rgbColorLookup[i].blue == blue - && qt_rgbColorLookup[i].alpha == alpha) { - return i; - } - } - return -1; -} + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(object); -static GstVideoSinkClass *sink_parent_class; - -#define VO_SINK(s) QmlVideoSurfaceGstSink *sink(reinterpret_cast<QmlVideoSurfaceGstSink *>(s)) - -QmlVideoSurfaceGstSink *QmlVideoSurfaceGstSink::createSink(QAbstractVideoSurface *surface) -{ - QmlVideoSurfaceGstSink *sink = reinterpret_cast<QmlVideoSurfaceGstSink *>( - g_object_new(QmlVideoSurfaceGstSink::get_type(), 0)); - - sink->delegate = new QmlVideoSurfaceGstDelegate(surface); - - return sink; -} - -GType QmlVideoSurfaceGstSink::get_type() -{ - static GType type = 0; - - if (type == 0) { - static const GTypeInfo info = - { - sizeof(QmlVideoSurfaceGstSinkClass), // class_size - base_init, // base_init - NULL, // base_finalize - class_init, // class_init - NULL, // class_finalize - NULL, // class_data - sizeof(QmlVideoSurfaceGstSink), // instance_size - 0, // n_preallocs - instance_init, // instance_init - 0 // value_table - }; - - type = g_type_register_static( - GST_TYPE_VIDEO_SINK, "QmlVideoSurfaceGstSink", &info, GTypeFlags(0)); + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + sink->surface->setForceAspectRatio(g_value_get_boolean(value)); + break; +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + case PROP_GLCONTEXT: + sink->surface->setGLContext(static_cast<QGLContext*>(g_value_get_pointer(value))); + break; +#endif + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; } - - return type; -} - -void QmlVideoSurfaceGstSink::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 = QmlVideoSurfaceGstSink::get_caps; - base_sink_class->set_caps = QmlVideoSurfaceGstSink::set_caps; - base_sink_class->buffer_alloc = QmlVideoSurfaceGstSink::buffer_alloc; - base_sink_class->start = QmlVideoSurfaceGstSink::start; - base_sink_class->stop = QmlVideoSurfaceGstSink::stop; - // base_sink_class->unlock = QmlVideoSurfaceGstSink::unlock; // Not implemented. - // base_sink_class->event = QmlVideoSurfaceGstSink::event; // Not implemented. - base_sink_class->preroll = QmlVideoSurfaceGstSink::preroll; - base_sink_class->render = QmlVideoSurfaceGstSink::render; - - GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class); - element_class->change_state = QmlVideoSurfaceGstSink::change_state; - - GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class); - object_class->finalize = QmlVideoSurfaceGstSink::finalize; -} - -void QmlVideoSurfaceGstSink::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-rgb, " - "framerate = (fraction) [ 0, MAX ], " - "width = (int) [ 1, MAX ], " - "height = (int) [ 1, MAX ]; " - "video/x-raw-yuv, " - "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 QmlVideoSurfaceGstSink::instance_init(GTypeInstance *instance, gpointer g_class) +void GstQtVideoSink::get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) { - VO_SINK(instance); + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(object); - Q_UNUSED(g_class); - - sink->delegate = 0; -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) - sink->pool = new QmlGstXvImageBufferPool(); + switch (prop_id) { + case PROP_FORCE_ASPECT_RATIO: + g_value_set_boolean(value, sink->surface->forceAspectRatio()); + break; +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + case PROP_GLCONTEXT: + g_value_set_pointer(value, sink->surface->glContext()); + break; #endif - sink->lastRequestedCaps = 0; - sink->lastBufferCaps = 0; - sink->lastSurfaceFormat = new QVideoSurfaceFormat; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } } -void QmlVideoSurfaceGstSink::finalize(GObject *object) +GstStateChangeReturn GstQtVideoSink::change_state(GstElement *element, GstStateChange transition) { - VO_SINK(object); -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) - delete sink->pool; - sink->pool = 0; -#endif - - delete sink->lastSurfaceFormat; - sink->lastSurfaceFormat = 0; - - if (sink->lastBufferCaps) - gst_caps_unref(sink->lastBufferCaps); - sink->lastBufferCaps = 0; + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(element); - if (sink->lastRequestedCaps) - gst_caps_unref(sink->lastRequestedCaps); - sink->lastRequestedCaps = 0; -} - -GstStateChangeReturn QmlVideoSurfaceGstSink::change_state( - GstElement *element, GstStateChange transition) -{ - VO_SINK(element); - - if (sink->delegate != NULL) { - switch (transition) { - case GST_STATE_CHANGE_READY_TO_PAUSED: - sink->delegate->setPlaying(true); - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - sink->delegate->setPlaying(false); - break; - default: - break; - } + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + sink->surface->setActive(true); + break; + case GST_STATE_CHANGE_PAUSED_TO_READY: + sink->surface->setActive(false); + break; + default: + break; } - return GST_ELEMENT_CLASS(sink_parent_class)->change_state( - element, transition); + return GST_ELEMENT_CLASS(s_parent_class)->change_state(element, transition); } -GstCaps *QmlVideoSurfaceGstSink::get_caps(GstBaseSink *base) +GstCaps *GstQtVideoSink::get_caps(GstBaseSink *base) { - VO_SINK(base); - - qDebug() << "QmlVideoSurfaceGstSink::get_caps"; - + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(base); GstCaps *caps = gst_caps_new_empty(); - foreach (QVideoFrame::PixelFormat format, sink->delegate->supportedPixelFormats()) { - int index = indexOfYuvColor(format); - - if (index != -1) { - GstStructure *structure = gst_structure_new( - "video/x-raw-yuv", - "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, - "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, - "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, - "format" , GST_TYPE_FOURCC, qt_yuvColorLookup[index].fourcc, - NULL); - - gst_caps_append_structure(caps, structure); - GST_DEBUG("%" GST_PTR_FORMAT, structure); - continue; - } - - const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); - - for (int i = 0; i < count; ++i) { - if (qt_rgbColorLookup[i].pixelFormat == format) { - GstStructure *structure = gst_structure_new( - "video/x-raw-rgb", - "framerate" , GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, - "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, - "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, - "bpp" , G_TYPE_INT, qt_rgbColorLookup[i].bitsPerPixel, - "depth" , G_TYPE_INT, qt_rgbColorLookup[i].depth, - "endianness", G_TYPE_INT, qt_rgbColorLookup[i].endianness, - "red_mask" , G_TYPE_INT, qt_rgbColorLookup[i].red, - "green_mask", G_TYPE_INT, qt_rgbColorLookup[i].green, - "blue_mask" , G_TYPE_INT, qt_rgbColorLookup[i].blue, - NULL); - - if (qt_rgbColorLookup[i].alpha != 0) { - gst_structure_set( - structure, "alpha_mask", G_TYPE_INT, qt_rgbColorLookup[i].alpha, NULL); - } - gst_caps_append_structure(caps, structure); - GST_DEBUG("%" GST_PTR_FORMAT, structure); - } - } + Q_FOREACH(BufferFormat::PixelFormat format, sink->surface->supportedPixelFormats()) { + gst_caps_append(caps, BufferFormat::pixelFormatToCaps(format)); } return caps; } -gboolean QmlVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps) +gboolean GstQtVideoSink::set_caps(GstBaseSink *base, GstCaps *caps) { - VO_SINK(base); - -#ifdef DEBUG_VIDEO_SURFACE_SINK - qDebug() << "set_caps:"; - qDebug() << gst_caps_to_string(caps); -#endif - - if (!caps) { - sink->delegate->stop(); - - return TRUE; - } else { - int bytesPerLine = 0; - QVideoSurfaceFormat format = formatForCaps(caps, &bytesPerLine); + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(base); - if (sink->delegate->isActive()) { - QVideoSurfaceFormat surfaceFormst = sink->delegate->surfaceFormat(); - - if (format.pixelFormat() == surfaceFormst.pixelFormat() && - format.frameSize() == surfaceFormst.frameSize()) - return TRUE; - else - sink->delegate->stop(); - } - - if (sink->lastRequestedCaps) - gst_caps_unref(sink->lastRequestedCaps); - sink->lastRequestedCaps = 0; - -#ifdef DEBUG_VIDEO_SURFACE_SINK - qDebug() << "Staring video surface, format:"; - qDebug() << format; - qDebug() << "bytesPerLine:" << bytesPerLine; -#endif - - if (sink->delegate->start(format, bytesPerLine)) - return TRUE; - else - qWarning() << "Failed to start video surface"; - } - - return FALSE; -} - -QVideoSurfaceFormat QmlVideoSurfaceGstSink::formatForCaps(GstCaps *caps, int *bytesPerLine) -{ - const GstStructure *structure = gst_caps_get_structure(caps, 0); - - QVideoFrame::PixelFormat pixelFormat = QVideoFrame::Format_Invalid; - int bitsPerPixel = 0; - - QSize size; - gst_structure_get_int(structure, "width", &size.rwidth()); - gst_structure_get_int(structure, "height", &size.rheight()); - - if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-yuv") == 0) { - guint32 fourcc = 0; - gst_structure_get_fourcc(structure, "format", &fourcc); - - int index = indexOfYuvColor(fourcc); - if (index != -1) { - pixelFormat = qt_yuvColorLookup[index].pixelFormat; - bitsPerPixel = qt_yuvColorLookup[index].bitsPerPixel; - } - } else if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) { - int depth = 0; - int endianness = 0; - int red = 0; - int green = 0; - int blue = 0; - int alpha = 0; - - gst_structure_get_int(structure, "bpp", &bitsPerPixel); - gst_structure_get_int(structure, "depth", &depth); - gst_structure_get_int(structure, "endianness", &endianness); - gst_structure_get_int(structure, "red_mask", &red); - gst_structure_get_int(structure, "green_mask", &green); - gst_structure_get_int(structure, "blue_mask", &blue); - gst_structure_get_int(structure, "alpha_mask", &alpha); - - int index = indexOfRgbColor(bitsPerPixel, depth, endianness, red, green, blue, alpha); - - if (index != -1) - pixelFormat = qt_rgbColorLookup[index].pixelFormat; - } - - if (pixelFormat != QVideoFrame::Format_Invalid) { - QVideoSurfaceFormat format(size, pixelFormat); - - QPair<int, int> rate; - gst_structure_get_fraction(structure, "framerate", &rate.first, &rate.second); - - if (rate.second) - format.setFrameRate(qreal(rate.first)/rate.second); - - gint aspectNum = 0; - gint aspectDenum = 0; - if (gst_structure_get_fraction( - structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { - if (aspectDenum > 0) - format.setPixelAspectRatio(aspectNum, aspectDenum); - } - - if (bytesPerLine) - *bytesPerLine = ((size.width() * bitsPerPixel / 8) + 3) & ~3; - - return format; - } - - return QVideoSurfaceFormat(); + GST_LOG_OBJECT(sink, "new caps %" GST_PTR_FORMAT, caps); + sink->formatDirty = true; + return TRUE; } - -GstFlowReturn QmlVideoSurfaceGstSink::buffer_alloc( - GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer) +GstFlowReturn GstQtVideoSink::buffer_alloc(GstBaseSink *base, guint64 offset, guint size, + GstCaps *caps, GstBuffer **buffer) { - VO_SINK(base); - + Q_UNUSED(base); Q_UNUSED(offset); Q_UNUSED(size); + Q_UNUSED(caps); *buffer = 0; -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) - - if (sink->lastRequestedCaps && gst_caps_is_equal(sink->lastRequestedCaps, caps)) { - //qDebug() << "reusing last caps"; - *buffer = GST_BUFFER(sink->pool->takeBuffer(*sink->lastSurfaceFormat, sink->lastBufferCaps)); - return GST_FLOW_OK; - } - - if (sink->delegate->supportedPixelFormats(QAbstractVideoBuffer::XvShmImageHandle).isEmpty()) { - //qDebug() << "sink doesn't support Xv buffers, skip buffers allocation"; - return GST_FLOW_OK; - } - - GstCaps *intersection = gst_caps_intersect(get_caps(GST_BASE_SINK(sink)), caps); - - if (gst_caps_is_empty (intersection)) { - gst_caps_unref(intersection); - return GST_FLOW_NOT_NEGOTIATED; - } - - if (sink->delegate->isActive()) { - //if format was changed, restart the surface - QVideoSurfaceFormat format = formatForCaps(intersection); - QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat(); - - if (format.pixelFormat() != surfaceFormat.pixelFormat() || - format.frameSize() != surfaceFormat.frameSize()) { -#ifdef DEBUG_VIDEO_SURFACE_SINK - qDebug() << "new format requested, restart video surface"; -#endif - sink->delegate->stop(); - } - } - - if (!sink->delegate->isActive()) { - int bytesPerLine = 0; - QVideoSurfaceFormat format = formatForCaps(intersection, &bytesPerLine); - - if (!sink->delegate->start(format, bytesPerLine)) { - qWarning() << "failed to start video surface"; - return GST_FLOW_NOT_NEGOTIATED; - } - } - - QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat(); - - if (!sink->pool->isFormatSupported(surfaceFormat)) { - //qDebug() << "sink doesn't provide Xv buffer details, skip buffers allocation"; - return GST_FLOW_OK; - } - - if (sink->lastRequestedCaps) - gst_caps_unref(sink->lastRequestedCaps); - sink->lastRequestedCaps = caps; - gst_caps_ref(sink->lastRequestedCaps); - - if (sink->lastBufferCaps) - gst_caps_unref(sink->lastBufferCaps); - sink->lastBufferCaps = intersection; - gst_caps_ref(sink->lastBufferCaps); - - *sink->lastSurfaceFormat = surfaceFormat; - - *buffer = GST_BUFFER(sink->pool->takeBuffer(surfaceFormat, intersection)); - -#endif return GST_FLOW_OK; } -gboolean QmlVideoSurfaceGstSink::start(GstBaseSink *base) +GstFlowReturn GstQtVideoSink::show_frame(GstVideoSink *video_sink, GstBuffer *buffer) { - Q_UNUSED(base); - - return TRUE; -} - -gboolean QmlVideoSurfaceGstSink::stop(GstBaseSink *base) -{ - Q_UNUSED(base); + GstQtVideoSink *sink = GST_QT_VIDEO_SINK(video_sink); - return TRUE; -} + GST_TRACE_OBJECT(sink, "Posting new buffer (%"GST_PTR_FORMAT") for rendering. " + "Format dirty: %d", buffer, (int)sink->formatDirty); -gboolean QmlVideoSurfaceGstSink::unlock(GstBaseSink *base) -{ - Q_UNUSED(base); + QCoreApplication::postEvent(sink->surface, + new GstQtVideoSinkSurface::BufferEvent(buffer, sink->formatDirty)); - return TRUE; -} - -gboolean QmlVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event) -{ - Q_UNUSED(base); - Q_UNUSED(event); - - return TRUE; -} - -GstFlowReturn QmlVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer) -{ - VO_SINK(base); - return sink->delegate->render(buffer); + sink->formatDirty = false; + return GST_FLOW_OK; } -GstFlowReturn QmlVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer) +void GstQtVideoSink::paint(GstQtVideoSink *sink, gpointer painter, + gint x, gint y, gint width, gint height) { - VO_SINK(base); - return sink->delegate->render(buffer); + sink->surface->paint(static_cast<QPainter*>(painter), x, y, width, height); } - diff --git a/elements/gstqtvideosink/gstqtvideosink.h b/elements/gstqtvideosink/gstqtvideosink.h index 49453ff..3f85ada 100644 --- a/elements/gstqtvideosink/gstqtvideosink.h +++ b/elements/gstqtvideosink/gstqtvideosink.h @@ -1,136 +1,97 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Collabora Ltd -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file was originally part of the Qt Mobility Components. -** -** 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. -** -****************************************************************************/ - -#ifndef VIDEOSURFACEGSTSINK_H -#define VIDEOSURFACEGSTSINK_H +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> -#include <gst/video/gstvideosink.h> - -#include <QtCore/qlist.h> -#include <QtCore/qmutex.h> -#include <QtCore/qqueue.h> -#include <QtCore/qpointer.h> -#include <QtCore/qwaitcondition.h> -#include <qvideosurfaceformat.h> -#include <qvideoframe.h> -#include <qabstractvideobuffer.h> - -QT_BEGIN_NAMESPACE -class QAbstractVideoSurface; -QT_END_NAMESPACE - -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) -class QmlGstXvImageBuffer; -class QmlGstXvImageBufferPool; -#endif - -class QmlVideoSurfaceGstDelegate : public QObject -{ - Q_OBJECT -public: - QmlVideoSurfaceGstDelegate(QAbstractVideoSurface *surface); - - QList<QVideoFrame::PixelFormat> supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const; + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. - QVideoSurfaceFormat surfaceFormat() const; + 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. - bool start(const QVideoSurfaceFormat &format, int bytesPerLine); - void stop(); + 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/>. +*/ - bool isActive(); +#ifndef GST_QT_VIDEO_SINK_H +#define GST_QT_VIDEO_SINK_H - void setPlaying(bool playing); - - GstFlowReturn render(GstBuffer *buffer); - -private slots: - void queuedStart(); - void queuedStop(); - void queuedRender(); +#include "gstqtvideosinkplugin.h" +#include <gst/video/gstvideosink.h> - void supportedFormatsChanged(); +#define GST_TYPE_QT_VIDEO_SINK \ + (GstQtVideoSink::get_type()) +#define GST_QT_VIDEO_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_QT_VIDEO_SINK,GstQtVideoSink)) +#define GST_QT_VIDEO_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_QT_VIDEO_SINK,GstQtVideoSinkClass)) -private: - QPointer<QAbstractVideoSurface> m_surface; - QList<QVideoFrame::PixelFormat> m_supportedPixelFormats; - QList<QVideoFrame::PixelFormat> m_supportedXVideoPixelFormats; - QMutex m_mutex; - QWaitCondition m_setupCondition; - QWaitCondition m_renderCondition; - QVideoSurfaceFormat m_format; - QVideoFrame m_frame; - GstFlowReturn m_renderReturn; - int m_bytesPerLine; - bool m_started; -}; +class GstQtVideoSinkSurface; -class QmlVideoSurfaceGstSink +struct GstQtVideoSink { public: GstVideoSink parent; - static QmlVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface); - static QVideoSurfaceFormat formatForCaps(GstCaps *caps, int *bytesPerLine = 0); + static GType get_type(); private: - static GType get_type(); - static void class_init(gpointer g_class, gpointer class_data); static void base_init(gpointer g_class); - static void instance_init(GTypeInstance *instance, gpointer g_class); - + static void class_init(gpointer g_class, gpointer class_data); + static void init(GTypeInstance *instance, gpointer g_class); static void finalize(GObject *object); + static void set_property(GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec); + static void get_property(GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec); + static GstStateChangeReturn change_state(GstElement *element, GstStateChange transition); static GstCaps *get_caps(GstBaseSink *sink); static gboolean set_caps(GstBaseSink *sink, GstCaps *caps); + static GstFlowReturn buffer_alloc(GstBaseSink *sink, guint64 offset, guint size, + GstCaps *caps, GstBuffer **buffer); - static GstFlowReturn buffer_alloc( - GstBaseSink *sink, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer); - - static gboolean start(GstBaseSink *sink); - static gboolean stop(GstBaseSink *sink); + static GstFlowReturn show_frame(GstVideoSink *sink, GstBuffer *buffer); - 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 void paint(GstQtVideoSink *sink, gpointer painter, + gint x, gint y, gint width, gint height); private: - QmlVideoSurfaceGstDelegate *delegate; - -#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) - QmlGstXvImageBufferPool *pool; + GstQtVideoSinkSurface *surface; + bool formatDirty; + + enum { + PROP_0, + PROP_FORCE_ASPECT_RATIO, +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + PROP_GLCONTEXT #endif + }; + + enum { + PAINT_SIGNAL, + UPDATE_SIGNAL, + LAST_SIGNAL + }; - GstCaps *lastRequestedCaps; - GstCaps *lastBufferCaps; - QVideoSurfaceFormat *lastSurfaceFormat; + static guint s_signals[LAST_SIGNAL]; + static GstVideoSinkClass *s_parent_class; + + friend class GstQtVideoSinkSurface; }; -class QmlVideoSurfaceGstSinkClass +struct GstQtVideoSinkClass { -public: GstVideoSinkClass parent_class; + + /* paint action signal */ + void (*paint) (GstQtVideoSink *sink, gpointer painter, + gint x, gint y, gint width, gint height); }; #endif diff --git a/elements/gstqtvideosink/gstqtvideosinkplugin.cpp b/elements/gstqtvideosink/gstqtvideosinkplugin.cpp new file mode 100644 index 0000000..b694609 --- /dev/null +++ b/elements/gstqtvideosink/gstqtvideosinkplugin.cpp @@ -0,0 +1,42 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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 "gstqtvideosinkplugin.h" +#include "gstqtvideosink.h" + +GST_DEBUG_CATEGORY(gst_qt_video_sink_debug); + +/* entry point to initialize the plug-in */ +static gboolean plugin_init(GstPlugin *plugin) +{ + GST_DEBUG_CATEGORY_INIT(gst_qt_video_sink_debug, "qtvideosink", 0, + "Debug category for GstQtVideoSink"); + + return gst_element_register(plugin, "qtvideosink", GST_RANK_NONE, GST_TYPE_QT_VIDEO_SINK); +} + +GST_PLUGIN_DEFINE ( + GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "qtvideosink", + "A video sink that can draw on any Qt surface", + plugin_init, + PACKAGE_VERSION, + "LGPL", + PACKAGE_NAME, + PACKAGE_ORIGIN +) diff --git a/elements/gstqtvideosink/gstqtvideosinkplugin.h b/elements/gstqtvideosink/gstqtvideosinkplugin.h new file mode 100644 index 0000000..3869ad6 --- /dev/null +++ b/elements/gstqtvideosink/gstqtvideosinkplugin.h @@ -0,0 +1,25 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ +#ifndef GST_QT_VIDEO_SINK_PLUGIN_H +#define GST_QT_VIDEO_SINK_PLUGIN_H + +#include <gst/gst.h> + +GST_DEBUG_CATEGORY_EXTERN(gst_qt_video_sink_debug); +#define GST_CAT_DEFAULT gst_qt_video_sink_debug + +#endif diff --git a/elements/gstqtvideosink/gstqtvideosinksurface.cpp b/elements/gstqtvideosink/gstqtvideosinksurface.cpp index 734a629..0b686ae 100644 --- a/elements/gstqtvideosink/gstqtvideosinksurface.cpp +++ b/elements/gstqtvideosink/gstqtvideosinksurface.cpp @@ -1,1698 +1,434 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Collabora Ltd -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file was originally part of the Qt Mobility Components. -** -** 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. -** -****************************************************************************/ - -#include "qmlpaintervideosurface.h" -//#include <qpaintervideosurface_mac_p.h> - -#include <qmath.h> - -#include <qpainter.h> -#include <qvariant.h> -#include <qvideosurfaceformat.h> - -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) -#include <qglshaderprogram.h> -#endif - -#include <QtDebug> -QT_BEGIN_NAMESPACE - -QmlVideoSurfacePainter::~QmlVideoSurfacePainter() -{ -} - -class QmlVideoSurfaceGenericPainter : public QmlVideoSurfacePainter -{ -public: - QmlVideoSurfaceGenericPainter(); - - QList<QVideoFrame::PixelFormat> supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const; - - bool isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *similar) const; - - QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format); - void stop(); - - QAbstractVideoSurface::Error setCurrentFrame(const QVideoFrame &frame); - - QAbstractVideoSurface::Error paint( - const QRectF &target, QPainter *painter, const QRectF &source); - - void updateColors(int brightness, int contrast, int hue, int saturation); - -private: - QList<QVideoFrame::PixelFormat> m_imagePixelFormats; - QVideoFrame m_frame; - QSize m_imageSize; - QImage::Format m_imageFormat; - QVideoSurfaceFormat::Direction m_scanLineDirection; -}; - -QmlVideoSurfaceGenericPainter::QmlVideoSurfaceGenericPainter() - : m_imageFormat(QImage::Format_Invalid) - , m_scanLineDirection(QVideoSurfaceFormat::TopToBottom) -{ - qDebug() << "GenericPainter"; - m_imagePixelFormats - << QVideoFrame::Format_RGB32 -#ifndef QT_OPENGL_ES // The raster formats should be a subset of the GL formats. - << QVideoFrame::Format_RGB24 -#endif - << QVideoFrame::Format_ARGB32 - << QVideoFrame::Format_RGB565; -} - -QList<QVideoFrame::PixelFormat> QmlVideoSurfaceGenericPainter::supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const -{ - qDebug() << "GenericPainter::supportedPixelFormats"; - switch (handleType) { - case QAbstractVideoBuffer::QPixmapHandle: - case QAbstractVideoBuffer::NoHandle: - return m_imagePixelFormats; - default: - ; - } - return QList<QVideoFrame::PixelFormat>(); -} +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> -bool QmlVideoSurfaceGenericPainter::isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *) const -{ - switch (format.handleType()) { - case QAbstractVideoBuffer::QPixmapHandle: - return true; - case QAbstractVideoBuffer::NoHandle: - return m_imagePixelFormats.contains(format.pixelFormat()) - && !format.frameSize().isEmpty(); - default: - ; - } - return false; -} + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. -QAbstractVideoSurface::Error QmlVideoSurfaceGenericPainter::start(const QVideoSurfaceFormat &format) -{ - m_frame = QVideoFrame(); - m_imageFormat = QVideoFrame::imageFormatFromPixelFormat(format.pixelFormat()); - m_imageSize = format.frameSize(); - m_scanLineDirection = format.scanLineDirection(); - - const QAbstractVideoBuffer::HandleType t = format.handleType(); - if (t == QAbstractVideoBuffer::NoHandle) { - if (m_imageFormat != QImage::Format_Invalid -#ifdef QT_OPENGL_ES - && format.pixelFormat() != QVideoFrame::Format_RGB24 -#endif - && !m_imageSize.isEmpty()) { - return QAbstractVideoSurface::NoError; - } - } else if (t == QAbstractVideoBuffer::QPixmapHandle) { - return QAbstractVideoSurface::NoError; - } - return QAbstractVideoSurface::UnsupportedFormatError; -} + 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. -void QmlVideoSurfaceGenericPainter::stop() -{ - m_frame = QVideoFrame(); -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGenericPainter::setCurrentFrame(const QVideoFrame &frame) -{ - m_frame = frame; - - return QAbstractVideoSurface::NoError; -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGenericPainter::paint( - const QRectF &target, QPainter *painter, const QRectF &source) -{ - if (m_frame.handleType() == QAbstractVideoBuffer::QPixmapHandle) { - painter->drawPixmap(target, m_frame.handle().value<QPixmap>(), source); - } else if (m_frame.map(QAbstractVideoBuffer::ReadOnly)) { - QImage image( - m_frame.bits(), - m_imageSize.width(), - m_imageSize.height(), - m_frame.bytesPerLine(), - m_imageFormat); - - if (m_scanLineDirection == QVideoSurfaceFormat::BottomToTop) { - const QTransform oldTransform = painter->transform(); - - painter->scale(1, -1); - painter->translate(0, -target.bottom()); - painter->drawImage( - QRectF(target.x(), 0, target.width(), target.height()), image, source); - painter->setTransform(oldTransform); - } else { - painter->drawImage(target, image, source); - } - - m_frame.unmap(); - } else if (m_frame.isValid()) { - return QAbstractVideoSurface::IncorrectFormatError; - } else { - painter->fillRect(target, Qt::black); - } - return QAbstractVideoSurface::NoError; -} - -void QmlVideoSurfaceGenericPainter::updateColors(int, int, int, int) -{ -} - -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) - -#ifndef Q_WS_MAC -# ifndef APIENTRYP -# ifdef APIENTRY -# define APIENTRYP APIENTRY * -# else -# define APIENTRY -# define APIENTRYP * -# endif -# endif -#else -# define APIENTRY -# define APIENTRYP * -#endif - -#ifndef GL_TEXTURE0 -# define GL_TEXTURE0 0x84C0 -# define GL_TEXTURE1 0x84C1 -# define GL_TEXTURE2 0x84C2 -#endif -#ifndef GL_PROGRAM_ERROR_STRING_ARB -# define GL_PROGRAM_ERROR_STRING_ARB 0x8874 -#endif - -#ifndef GL_UNSIGNED_SHORT_5_6_5 -# define GL_UNSIGNED_SHORT_5_6_5 33635 -#endif - -class QmlVideoSurfaceGLPainter : public QmlVideoSurfacePainter -{ -public: - QmlVideoSurfaceGLPainter(QGLContext *context); - ~QmlVideoSurfaceGLPainter(); - QList<QVideoFrame::PixelFormat> supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const; - - bool isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *similar) const; - - QAbstractVideoSurface::Error setCurrentFrame(const QVideoFrame &frame); - - QAbstractVideoSurface::Error paint( - const QRectF &target, QPainter *painter, const QRectF &source); - - void updateColors(int brightness, int contrast, int hue, int saturation); - void viewportDestroyed(); - -protected: - void initRgbTextureInfo(GLenum internalFormat, GLuint format, GLenum type, const QSize &size); - void initYuv420PTextureInfo(const QSize &size); - void initYv12TextureInfo(const QSize &size); - -#ifndef QT_OPENGL_ES - typedef void (APIENTRY *_glActiveTexture) (GLenum); - _glActiveTexture glActiveTexture; -#endif - - QList<QVideoFrame::PixelFormat> m_imagePixelFormats; - QList<QVideoFrame::PixelFormat> m_glPixelFormats; - QMatrix4x4 m_colorMatrix; - QVideoFrame m_frame; - - QGLContext *m_context; - QAbstractVideoBuffer::HandleType m_handleType; - QVideoSurfaceFormat::Direction m_scanLineDirection; - QVideoSurfaceFormat::YCbCrColorSpace m_colorSpace; - GLenum m_textureFormat; - GLuint m_textureInternalFormat; - GLenum m_textureType; - int m_textureCount; - GLuint m_textureIds[3]; - int m_textureWidths[3]; - int m_textureHeights[3]; - int m_textureOffsets[3]; - bool m_yuv; -}; - -QmlVideoSurfaceGLPainter::QmlVideoSurfaceGLPainter(QGLContext *context) - : m_context(context) - , m_handleType(QAbstractVideoBuffer::NoHandle) - , m_scanLineDirection(QVideoSurfaceFormat::TopToBottom) - , m_colorSpace(QVideoSurfaceFormat::YCbCr_BT601) - , m_textureFormat(0) - , m_textureInternalFormat(0) - , m_textureType(0) - , m_textureCount(0) - , m_yuv(false) -{ -#ifndef QT_OPENGL_ES - glActiveTexture = (_glActiveTexture)m_context->getProcAddress(QLatin1String("glActiveTexture")); -#endif -} - -QmlVideoSurfaceGLPainter::~QmlVideoSurfaceGLPainter() -{ -} - -void QmlVideoSurfaceGLPainter::viewportDestroyed() -{ - m_context = 0; -} - -QList<QVideoFrame::PixelFormat> QmlVideoSurfaceGLPainter::supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const -{ - qDebug() << "GLPainter::supportedPixelFormats"; - switch (handleType) { - case QAbstractVideoBuffer::NoHandle: - return m_imagePixelFormats; - case QAbstractVideoBuffer::QPixmapHandle: - case QAbstractVideoBuffer::GLTextureHandle: - return m_glPixelFormats; - default: - ; - } - return QList<QVideoFrame::PixelFormat>(); -} - -bool QmlVideoSurfaceGLPainter::isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *) const -{ - if (format.frameSize().isEmpty()) { - return false; - } else { - switch (format.handleType()) { - case QAbstractVideoBuffer::NoHandle: - return m_imagePixelFormats.contains(format.pixelFormat()); - case QAbstractVideoBuffer::QPixmapHandle: - case QAbstractVideoBuffer::GLTextureHandle: - return m_glPixelFormats.contains(format.pixelFormat()); - default: - ; - } - } - return false; -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGLPainter::setCurrentFrame(const QVideoFrame &frame) -{ - m_frame = frame; - - if (m_handleType == QAbstractVideoBuffer::GLTextureHandle) { - m_textureIds[0] = frame.handle().toInt(); - } else if (m_frame.map(QAbstractVideoBuffer::ReadOnly)) { - m_context->makeCurrent(); - - for (int i = 0; i < m_textureCount; ++i) { - glBindTexture(GL_TEXTURE_2D, m_textureIds[i]); - glTexImage2D( - GL_TEXTURE_2D, - 0, - m_textureInternalFormat, - m_textureWidths[i], - m_textureHeights[i], - 0, - m_textureFormat, - m_textureType, - m_frame.bits() + m_textureOffsets[i]); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } - m_frame.unmap(); - } else if (m_handleType != QAbstractVideoBuffer::QPixmapHandle && m_frame.isValid()) { - return QAbstractVideoSurface::IncorrectFormatError; - } - - return QAbstractVideoSurface::NoError; -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGLPainter::paint( - const QRectF &target, QPainter *painter, const QRectF &source) -{ - if (m_frame.handleType() == QAbstractVideoBuffer::QPixmapHandle) { - painter->drawPixmap(target, m_frame.handle().value<QPixmap>(), source); - } else if (m_frame.isValid()) { - return QAbstractVideoSurface::IncorrectFormatError; - } else { - painter->fillRect(target, Qt::black); - } - return QAbstractVideoSurface::NoError; -} - -void QmlVideoSurfaceGLPainter::updateColors(int brightness, int contrast, int hue, int saturation) -{ - const qreal b = brightness / 200.0; - const qreal c = contrast / 100.0 + 1.0; - const qreal h = hue / 100.0; - const qreal s = saturation / 100.0 + 1.0; - - const qreal cosH = qCos(M_PI * h); - const qreal sinH = qSin(M_PI * h); - - const qreal h11 = 0.787 * cosH - 0.213 * sinH + 0.213; - const qreal h21 = -0.213 * cosH + 0.143 * sinH + 0.213; - const qreal h31 = -0.213 * cosH - 0.787 * sinH + 0.213; - - const qreal h12 = -0.715 * cosH - 0.715 * sinH + 0.715; - const qreal h22 = 0.285 * cosH + 0.140 * sinH + 0.715; - const qreal h32 = -0.715 * cosH + 0.715 * sinH + 0.715; - - const qreal h13 = -0.072 * cosH + 0.928 * sinH + 0.072; - const qreal h23 = -0.072 * cosH - 0.283 * sinH + 0.072; - const qreal h33 = 0.928 * cosH + 0.072 * sinH + 0.072; - - const qreal sr = (1.0 - s) * 0.3086; - const qreal sg = (1.0 - s) * 0.6094; - const qreal sb = (1.0 - s) * 0.0820; - - const qreal sr_s = sr + s; - const qreal sg_s = sg + s; - const qreal sb_s = sr + s; - - const float m4 = (s + sr + sg + sb) * (0.5 - 0.5 * c + b); - - m_colorMatrix(0, 0) = c * (sr_s * h11 + sg * h21 + sb * h31); - m_colorMatrix(0, 1) = c * (sr_s * h12 + sg * h22 + sb * h32); - m_colorMatrix(0, 2) = c * (sr_s * h13 + sg * h23 + sb * h33); - m_colorMatrix(0, 3) = m4; - - m_colorMatrix(1, 0) = c * (sr * h11 + sg_s * h21 + sb * h31); - m_colorMatrix(1, 1) = c * (sr * h12 + sg_s * h22 + sb * h32); - m_colorMatrix(1, 2) = c * (sr * h13 + sg_s * h23 + sb * h33); - m_colorMatrix(1, 3) = m4; - - m_colorMatrix(2, 0) = c * (sr * h11 + sg * h21 + sb_s * h31); - m_colorMatrix(2, 1) = c * (sr * h12 + sg * h22 + sb_s * h32); - m_colorMatrix(2, 2) = c * (sr * h13 + sg * h23 + sb_s * h33); - m_colorMatrix(2, 3) = m4; - - m_colorMatrix(3, 0) = 0.0; - m_colorMatrix(3, 1) = 0.0; - m_colorMatrix(3, 2) = 0.0; - m_colorMatrix(3, 3) = 1.0; - - if (m_yuv) { - QMatrix4x4 colorSpaceMatrix; - - switch (m_colorSpace) { - case QVideoSurfaceFormat::YCbCr_JPEG: - colorSpaceMatrix = QMatrix4x4( - 1.0, 0.000, 1.402, -0.701, - 1.0, -0.344, -0.714, 0.529, - 1.0, 1.772, 0.000, -0.886, - 0.0, 0.000, 0.000, 1.0000); - break; - case QVideoSurfaceFormat::YCbCr_BT709: - case QVideoSurfaceFormat::YCbCr_xvYCC709: - colorSpaceMatrix = QMatrix4x4( - 1.164, 0.000, 1.793, -0.5727, - 1.164, -0.534, -0.213, 0.3007, - 1.164, 2.115, 0.000, -1.1302, - 0.0, 0.000, 0.000, 1.0000); - break; - default: //BT 601: - colorSpaceMatrix = QMatrix4x4( - 1.164, 0.000, 1.596, -0.8708, - 1.164, -0.392, -0.813, 0.5296, - 1.164, 2.017, 0.000, -1.081, - 0.0, 0.000, 0.000, 1.0000); - } - - m_colorMatrix = m_colorMatrix * colorSpaceMatrix; - } -} - -void QmlVideoSurfaceGLPainter::initRgbTextureInfo( - GLenum internalFormat, GLuint format, GLenum type, const QSize &size) -{ - m_yuv = false; - m_textureInternalFormat = internalFormat; - m_textureFormat = format; - m_textureType = type; - m_textureCount = 1; - m_textureWidths[0] = size.width(); - m_textureHeights[0] = size.height(); - m_textureOffsets[0] = 0; -} - -void QmlVideoSurfaceGLPainter::initYuv420PTextureInfo(const QSize &size) -{ - int bytesPerLine = (size.width() + 3) & ~3; - int bytesPerLine2 = (size.width() / 2 + 3) & ~3; - - m_yuv = true; - m_textureInternalFormat = GL_LUMINANCE; - m_textureFormat = GL_LUMINANCE; - m_textureType = GL_UNSIGNED_BYTE; - m_textureCount = 3; - m_textureWidths[0] = bytesPerLine; - m_textureHeights[0] = size.height(); - m_textureOffsets[0] = 0; - m_textureWidths[1] = bytesPerLine2; - m_textureHeights[1] = size.height() / 2; - m_textureOffsets[1] = bytesPerLine * size.height(); - m_textureWidths[2] = bytesPerLine2; - m_textureHeights[2] = size.height() / 2; - m_textureOffsets[2] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; -} - -void QmlVideoSurfaceGLPainter::initYv12TextureInfo(const QSize &size) -{ - int bytesPerLine = (size.width() + 3) & ~3; - int bytesPerLine2 = (size.width() / 2 + 3) & ~3; - - m_yuv = true; - m_textureInternalFormat = GL_LUMINANCE; - m_textureFormat = GL_LUMINANCE; - m_textureType = GL_UNSIGNED_BYTE; - m_textureCount = 3; - m_textureWidths[0] = bytesPerLine; - m_textureHeights[0] = size.height(); - m_textureOffsets[0] = 0; - m_textureWidths[1] = bytesPerLine2; - m_textureHeights[1] = size.height() / 2; - m_textureOffsets[1] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; - m_textureWidths[2] = bytesPerLine2; - m_textureHeights[2] = size.height() / 2; - m_textureOffsets[2] = bytesPerLine * size.height(); -} - -#ifndef QT_OPENGL_ES - -# ifndef GL_FRAGMENT_PROGRAM_ARB -# define GL_FRAGMENT_PROGRAM_ARB 0x8804 -# define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 -# endif - -// Paints an RGB32 frame -static const char *qt_arbfp_xrgbShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP xrgb;\n" - "TEX xrgb.xyz, fragment.texcoord[0], texture[0], 2D;\n" - "MOV xrgb.w, matrix[3].w;\n" - "DP4 result.color.x, xrgb.zyxw, matrix[0];\n" - "DP4 result.color.y, xrgb.zyxw, matrix[1];\n" - "DP4 result.color.z, xrgb.zyxw, matrix[2];\n" - "END"; - -// Paints an ARGB frame. -static const char *qt_arbfp_argbShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP argb;\n" - "TEX argb, fragment.texcoord[0], texture[0], 2D;\n" - "MOV argb.w, matrix[3].w;\n" - "DP4 result.color.x, argb.zyxw, matrix[0];\n" - "DP4 result.color.y, argb.zyxw, matrix[1];\n" - "DP4 result.color.z, argb.zyxw, matrix[2];\n" - "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" - "END"; - -// Paints an RGB(A) frame. -static const char *qt_arbfp_rgbShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP rgb;\n" - "TEX rgb, fragment.texcoord[0], texture[0], 2D;\n" - "MOV rgb.w, matrix[3].w;\n" - "DP4 result.color.x, rgb, matrix[0];\n" - "DP4 result.color.y, rgb, matrix[1];\n" - "DP4 result.color.z, rgb, matrix[2];\n" - "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" - "END"; - -// Paints a YUV420P or YV12 frame. -static const char *qt_arbfp_yuvPlanarShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP yuv;\n" - "TEX yuv.x, fragment.texcoord[0], texture[0], 2D;\n" - "TEX yuv.y, fragment.texcoord[0], texture[1], 2D;\n" - "TEX yuv.z, fragment.texcoord[0], texture[2], 2D;\n" - "MOV yuv.w, matrix[3].w;\n" - "DP4 result.color.x, yuv, matrix[0];\n" - "DP4 result.color.y, yuv, matrix[1];\n" - "DP4 result.color.z, yuv, matrix[2];\n" - "END"; - -// Paints a YUV444 frame. -static const char *qt_arbfp_xyuvShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP ayuv;\n" - "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" - "MOV ayuv.x, matrix[3].w;\n" - "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" - "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" - "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" - "END"; - -// Paints a AYUV444 frame. -static const char *qt_arbfp_ayuvShaderProgram = - "!!ARBfp1.0\n" - "PARAM matrix[4] = { program.local[0..2]," - "{ 0.0, 0.0, 0.0, 1.0 } };\n" - "TEMP ayuv;\n" - "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" - "MOV ayuv.x, matrix[3].w;\n" - "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" - "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" - "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" - "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" - "END"; - -class QVideoSurfaceArbFpPainter : public QmlVideoSurfaceGLPainter -{ -public: - QVideoSurfaceArbFpPainter(QGLContext *context); - - QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format); - void stop(); - - QAbstractVideoSurface::Error paint( - const QRectF &target, QPainter *painter, const QRectF &source); - -private: - typedef void (APIENTRY *_glProgramStringARB) (GLenum, GLenum, GLsizei, const GLvoid *); - typedef void (APIENTRY *_glBindProgramARB) (GLenum, GLuint); - typedef void (APIENTRY *_glDeleteProgramsARB) (GLsizei, const GLuint *); - typedef void (APIENTRY *_glGenProgramsARB) (GLsizei, GLuint *); - typedef void (APIENTRY *_glProgramLocalParameter4fARB) ( - GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); - typedef void (APIENTRY *_glActiveTexture) (GLenum); - - _glProgramStringARB glProgramStringARB; - _glBindProgramARB glBindProgramARB; - _glDeleteProgramsARB glDeleteProgramsARB; - _glGenProgramsARB glGenProgramsARB; - _glProgramLocalParameter4fARB glProgramLocalParameter4fARB; - - GLuint m_programId; - QSize m_frameSize; -}; - -QVideoSurfaceArbFpPainter::QVideoSurfaceArbFpPainter(QGLContext *context) - : QmlVideoSurfaceGLPainter(context) - , m_programId(0) -{ - qDebug() << "ArbFpPainter"; - glProgramStringARB = (_glProgramStringARB) m_context->getProcAddress( - QLatin1String("glProgramStringARB")); - glBindProgramARB = (_glBindProgramARB) m_context->getProcAddress( - QLatin1String("glBindProgramARB")); - glDeleteProgramsARB = (_glDeleteProgramsARB) m_context->getProcAddress( - QLatin1String("glDeleteProgramsARB")); - glGenProgramsARB = (_glGenProgramsARB) m_context->getProcAddress( - QLatin1String("glGenProgramsARB")); - glProgramLocalParameter4fARB = (_glProgramLocalParameter4fARB) m_context->getProcAddress( - QLatin1String("glProgramLocalParameter4fARB")); - - m_imagePixelFormats - << QVideoFrame::Format_RGB32 - << QVideoFrame::Format_BGR32 - << QVideoFrame::Format_ARGB32 - << QVideoFrame::Format_RGB24 - << QVideoFrame::Format_BGR24 - << QVideoFrame::Format_RGB565 - << QVideoFrame::Format_AYUV444 - << QVideoFrame::Format_YUV444 - << QVideoFrame::Format_YV12 - << QVideoFrame::Format_YUV420P; - m_glPixelFormats - << QVideoFrame::Format_RGB32 - << QVideoFrame::Format_ARGB32; -} - -QAbstractVideoSurface::Error QVideoSurfaceArbFpPainter::start(const QVideoSurfaceFormat &format) -{ - Q_ASSERT(m_textureCount == 0); - - QAbstractVideoSurface::Error error = QAbstractVideoSurface::NoError; - - m_context->makeCurrent(); - - const char *program = 0; - - if (format.handleType() == QAbstractVideoBuffer::NoHandle) { - switch (format.pixelFormat()) { - case QVideoFrame::Format_RGB32: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_xrgbShaderProgram; - break; - case QVideoFrame::Format_BGR32: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_rgbShaderProgram; - break; - case QVideoFrame::Format_ARGB32: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_argbShaderProgram; - break; - case QVideoFrame::Format_RGB24: - initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_rgbShaderProgram; - break; - case QVideoFrame::Format_BGR24: - initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_xrgbShaderProgram; - break; - case QVideoFrame::Format_RGB565: - initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); - program = qt_arbfp_rgbShaderProgram; - break; - case QVideoFrame::Format_YUV444: - initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_xyuvShaderProgram; - m_yuv = true; - break; - case QVideoFrame::Format_AYUV444: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - program = qt_arbfp_ayuvShaderProgram; - m_yuv = true; - break; - case QVideoFrame::Format_YV12: - initYv12TextureInfo(format.frameSize()); - program = qt_arbfp_yuvPlanarShaderProgram; - break; - case QVideoFrame::Format_YUV420P: - initYuv420PTextureInfo(format.frameSize()); - program = qt_arbfp_yuvPlanarShaderProgram; - break; - default: - break; - } - } else if (format.handleType() == QAbstractVideoBuffer::GLTextureHandle) { - switch (format.pixelFormat()) { - case QVideoFrame::Format_RGB32: - case QVideoFrame::Format_ARGB32: - m_yuv = false; - m_textureCount = 1; - program = qt_arbfp_rgbShaderProgram; - break; - default: - break; - } - } else if (format.handleType() == QAbstractVideoBuffer::QPixmapHandle) { - m_handleType = QAbstractVideoBuffer::QPixmapHandle; - return QAbstractVideoSurface::NoError; - } - - if (!program) { - error = QAbstractVideoSurface::UnsupportedFormatError; - } else { - glGenProgramsARB(1, &m_programId); - - GLenum glError = glGetError(); - if (glError != GL_NO_ERROR) { - qWarning("QmlPainterVideoSurface: ARBfb Shader allocation error %x", int(glError)); - m_textureCount = 0; - m_programId = 0; - - error = QAbstractVideoSurface::ResourceError; - } else { - glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); - glProgramStringARB( - GL_FRAGMENT_PROGRAM_ARB, - GL_PROGRAM_FORMAT_ASCII_ARB, - qstrlen(program), - reinterpret_cast<const GLvoid *>(program)); - - if ((glError = glGetError()) != GL_NO_ERROR) { - const GLubyte* errorString = glGetString(GL_PROGRAM_ERROR_STRING_ARB); - - qWarning("QmlPainterVideoSurface: ARBfp Shader compile error %x, %s", - int(glError), - reinterpret_cast<const char *>(errorString)); - glDeleteProgramsARB(1, &m_programId); - - m_textureCount = 0; - m_programId = 0; - - error = QAbstractVideoSurface::ResourceError; - } else { - m_handleType = format.handleType(); - m_scanLineDirection = format.scanLineDirection(); - m_frameSize = format.frameSize(); - m_colorSpace = format.yCbCrColorSpace(); - - if (m_handleType == QAbstractVideoBuffer::NoHandle) - glGenTextures(m_textureCount, m_textureIds); - } - } - } - - return error; -} - -void QVideoSurfaceArbFpPainter::stop() -{ - if (m_context) { - m_context->makeCurrent(); - - if (m_handleType != QAbstractVideoBuffer::GLTextureHandle) - glDeleteTextures(m_textureCount, m_textureIds); - glDeleteProgramsARB(1, &m_programId); - } - - m_textureCount = 0; - m_programId = 0; - m_handleType = QAbstractVideoBuffer::NoHandle; -} - -QAbstractVideoSurface::Error QVideoSurfaceArbFpPainter::paint( - const QRectF &target, QPainter *painter, const QRectF &source) -{ - const QAbstractVideoBuffer::HandleType h = m_frame.handleType(); - if (h == QAbstractVideoBuffer::NoHandle || h == QAbstractVideoBuffer::GLTextureHandle) { - bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); - bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); - - painter->beginNativePainting(); - - if (stencilTestEnabled) - glEnable(GL_STENCIL_TEST); - if (scissorTestEnabled) - glEnable(GL_SCISSOR_TEST); - - const float txLeft = source.left() / m_frameSize.width(); - const float txRight = source.right() / m_frameSize.width(); - const float txTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? source.top() / m_frameSize.height() - : source.bottom() / m_frameSize.height(); - const float txBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? source.bottom() / m_frameSize.height() - : source.top() / m_frameSize.height(); - - const float tx_array[] = - { - txLeft , txBottom, - txRight, txBottom, - txLeft , txTop, - txRight, txTop - }; - - const GLfloat vTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? target.top() - : target.bottom() + 1; - const GLfloat vBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? target.bottom() + 1 - : target.top(); - - const GLfloat v_array[] = - { - GLfloat(target.left()) , GLfloat(vBottom), - GLfloat(target.right() + 1), GLfloat(vBottom), - GLfloat(target.left()) , GLfloat(vTop), - GLfloat(target.right() + 1), GLfloat(vTop) - }; - - glEnable(GL_FRAGMENT_PROGRAM_ARB); - glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); - - glProgramLocalParameter4fARB( - GL_FRAGMENT_PROGRAM_ARB, - 0, - m_colorMatrix(0, 0), - m_colorMatrix(0, 1), - m_colorMatrix(0, 2), - m_colorMatrix(0, 3)); - glProgramLocalParameter4fARB( - GL_FRAGMENT_PROGRAM_ARB, - 1, - m_colorMatrix(1, 0), - m_colorMatrix(1, 1), - m_colorMatrix(1, 2), - m_colorMatrix(1, 3)); - glProgramLocalParameter4fARB( - GL_FRAGMENT_PROGRAM_ARB, - 2, - m_colorMatrix(2, 0), - m_colorMatrix(2, 1), - m_colorMatrix(2, 2), - m_colorMatrix(2, 3)); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); - - if (m_textureCount == 3) { - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); - glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); - glActiveTexture(GL_TEXTURE0); - } - - glVertexPointer(2, GL_FLOAT, 0, v_array); - glTexCoordPointer(2, GL_FLOAT, 0, tx_array); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - glDisable(GL_FRAGMENT_PROGRAM_ARB); - - painter->endNativePainting(); - - return QAbstractVideoSurface::NoError; - } - - return QmlVideoSurfaceGLPainter::paint(target, painter, source); -} - -#endif - -static const char *qt_glsl_vertexShaderProgram = - "attribute highp vec4 vertexCoordArray;\n" - "attribute highp vec2 textureCoordArray;\n" - "uniform highp mat4 positionMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " gl_Position = positionMatrix * vertexCoordArray;\n" - " textureCoord = textureCoordArray;\n" - "}\n"; - -// Paints an RGB32 frame -static const char *qt_glsl_xrgbShaderProgram = - "uniform sampler2D texRgb;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" - " gl_FragColor = colorMatrix * color;\n" - "}\n"; - -// Paints an ARGB frame. -static const char *qt_glsl_argbShaderProgram = - "uniform sampler2D texRgb;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" - " color = colorMatrix * color;\n" - " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" - "}\n"; - -// Paints an RGB(A) frame. -static const char *qt_glsl_rgbShaderProgram = - "uniform sampler2D texRgb;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).rgb, 1.0);\n" - " color = colorMatrix * color;\n" - " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" - "}\n"; - -// Paints a YUV420P or YV12 frame. -static const char *qt_glsl_yuvPlanarShaderProgram = - "uniform sampler2D texY;\n" - "uniform sampler2D texU;\n" - "uniform sampler2D texV;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(\n" - " texture2D(texY, textureCoord.st).r,\n" - " texture2D(texU, textureCoord.st).r,\n" - " texture2D(texV, textureCoord.st).r,\n" - " 1.0);\n" - " gl_FragColor = colorMatrix * color;\n" - "}\n"; - -// Paints a YUV444 frame. -static const char *qt_glsl_xyuvShaderProgram = - "uniform sampler2D texRgb;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" - " gl_FragColor = colorMatrix * color;\n" - "}\n"; - -// Paints a AYUV444 frame. -static const char *qt_glsl_ayuvShaderProgram = - "uniform sampler2D texRgb;\n" - "uniform mediump mat4 colorMatrix;\n" - "varying highp vec2 textureCoord;\n" - "void main(void)\n" - "{\n" - " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" - " color = colorMatrix * color;\n" - " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).r);\n" - "}\n"; - -class QmlVideoSurfaceGlslPainter : public QmlVideoSurfaceGLPainter -{ -public: - QmlVideoSurfaceGlslPainter(QGLContext *context); - - QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format); - void stop(); - - QAbstractVideoSurface::Error paint( - const QRectF &target, QPainter *painter, const QRectF &source); - -private: - QGLShaderProgram m_program; - QSize m_frameSize; -}; - -QmlVideoSurfaceGlslPainter::QmlVideoSurfaceGlslPainter(QGLContext *context) - : QmlVideoSurfaceGLPainter(context) - , m_program(context) -{ - qDebug() << "GlslPainter"; - m_imagePixelFormats - << QVideoFrame::Format_YUV420P - << QVideoFrame::Format_YV12 - << QVideoFrame::Format_RGB32 - << QVideoFrame::Format_BGR32 -#if !defined(QT_OPENGL_ES) && !defined(QT_OPENGL_ES_2) - << QVideoFrame::Format_RGB24 - << QVideoFrame::Format_BGR24 - - << QVideoFrame::Format_RGB565 - << QVideoFrame::Format_AYUV444 - << QVideoFrame::Format_YUV444 -#endif - << QVideoFrame::Format_ARGB32; - - - m_glPixelFormats - << QVideoFrame::Format_RGB32 - << QVideoFrame::Format_ARGB32; -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGlslPainter::start(const QVideoSurfaceFormat &format) -{ - Q_ASSERT(m_textureCount == 0); - - QAbstractVideoSurface::Error error = QAbstractVideoSurface::NoError; - - m_context->makeCurrent(); - - const char *fragmentProgram = 0; - - if (format.handleType() == QAbstractVideoBuffer::NoHandle) { - switch (format.pixelFormat()) { - case QVideoFrame::Format_RGB32: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_xrgbShaderProgram; - break; - case QVideoFrame::Format_BGR32: - initRgbTextureInfo(GL_RGB, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_rgbShaderProgram; - break; - case QVideoFrame::Format_ARGB32: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_argbShaderProgram; - break; -#ifndef QT_OPENGL_ES - case QVideoFrame::Format_RGB24: - initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_rgbShaderProgram; - break; - case QVideoFrame::Format_BGR24: - initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_argbShaderProgram; - break; -#endif - case QVideoFrame::Format_RGB565: - initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); - fragmentProgram = qt_glsl_rgbShaderProgram; - break; - case QVideoFrame::Format_YUV444: - initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_xyuvShaderProgram; - m_yuv = true; - break; - case QVideoFrame::Format_AYUV444: - initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); - fragmentProgram = qt_glsl_ayuvShaderProgram; - m_yuv = true; - break; - case QVideoFrame::Format_YV12: - initYv12TextureInfo(format.frameSize()); - fragmentProgram = qt_glsl_yuvPlanarShaderProgram; - break; - case QVideoFrame::Format_YUV420P: - initYuv420PTextureInfo(format.frameSize()); - fragmentProgram = qt_glsl_yuvPlanarShaderProgram; - break; - default: - break; - } - } else if (format.handleType() == QAbstractVideoBuffer::GLTextureHandle) { - switch (format.pixelFormat()) { - case QVideoFrame::Format_RGB32: - case QVideoFrame::Format_ARGB32: - m_yuv = false; - m_textureCount = 1; - fragmentProgram = qt_glsl_rgbShaderProgram; - break; - default: - break; - } - } else if (format.handleType() == QAbstractVideoBuffer::QPixmapHandle) { - m_handleType = QAbstractVideoBuffer::QPixmapHandle; - return QAbstractVideoSurface::NoError; - } - - if (!fragmentProgram) { - error = QAbstractVideoSurface::UnsupportedFormatError; - } else if (!m_program.addShaderFromSourceCode(QGLShader::Vertex, qt_glsl_vertexShaderProgram)) { - qWarning("QmlPainterVideoSurface: Vertex shader compile error %s", - qPrintable(m_program.log())); - error = QAbstractVideoSurface::ResourceError; - } else if (!m_program.addShaderFromSourceCode(QGLShader::Fragment, fragmentProgram)) { - qWarning("QmlPainterVideoSurface: Shader compile error %s", qPrintable(m_program.log())); - error = QAbstractVideoSurface::ResourceError; - m_program.removeAllShaders(); - } else if(!m_program.link()) { - qWarning("QmlPainterVideoSurface: Shader link error %s", qPrintable(m_program.log())); - m_program.removeAllShaders(); - error = QAbstractVideoSurface::ResourceError; - } else { - m_handleType = format.handleType(); - m_scanLineDirection = format.scanLineDirection(); - m_frameSize = format.frameSize(); - m_colorSpace = format.yCbCrColorSpace(); - - if (m_handleType == QAbstractVideoBuffer::NoHandle) - glGenTextures(m_textureCount, m_textureIds); - } - - return error; -} - -void QmlVideoSurfaceGlslPainter::stop() -{ - if (m_context) { - m_context->makeCurrent(); - - if (m_handleType != QAbstractVideoBuffer::GLTextureHandle) - glDeleteTextures(m_textureCount, m_textureIds); - } - - m_program.removeAllShaders(); - - m_textureCount = 0; - m_handleType = QAbstractVideoBuffer::NoHandle; -} - -QAbstractVideoSurface::Error QmlVideoSurfaceGlslPainter::paint( - const QRectF &target, QPainter *painter, const QRectF &source) -{ - const QAbstractVideoBuffer::HandleType h = m_frame.handleType(); - if (h == QAbstractVideoBuffer::NoHandle || h == QAbstractVideoBuffer::GLTextureHandle) { - bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); - bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); - - painter->beginNativePainting(); - - if (stencilTestEnabled) - glEnable(GL_STENCIL_TEST); - if (scissorTestEnabled) - glEnable(GL_SCISSOR_TEST); - - const int width = QGLContext::currentContext()->device()->width(); - const int height = QGLContext::currentContext()->device()->height(); - - const QTransform transform = painter->deviceTransform(); - - const GLfloat wfactor = 2.0 / width; - const GLfloat hfactor = -2.0 / height; - - const GLfloat positionMatrix[4][4] = - { - { - /*(0,0)*/ GLfloat(wfactor * transform.m11() - transform.m13()), - /*(0,1)*/ GLfloat(hfactor * transform.m12() + transform.m13()), - /*(0,2)*/ 0.0, - /*(0,3)*/ GLfloat(transform.m13()) - }, { - /*(1,0)*/ GLfloat(wfactor * transform.m21() - transform.m23()), - /*(1,1)*/ GLfloat(hfactor * transform.m22() + transform.m23()), - /*(1,2)*/ 0.0, - /*(1,3)*/ GLfloat(transform.m23()) - }, { - /*(2,0)*/ 0.0, - /*(2,1)*/ 0.0, - /*(2,2)*/ -1.0, - /*(2,3)*/ 0.0 - }, { - /*(3,0)*/ GLfloat(wfactor * transform.dx() - transform.m33()), - /*(3,1)*/ GLfloat(hfactor * transform.dy() + transform.m33()), - /*(3,2)*/ 0.0, - /*(3,3)*/ GLfloat(transform.m33()) - } - }; - - const GLfloat vTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? target.top() - : target.bottom() + 1; - const GLfloat vBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? target.bottom() + 1 - : target.top(); - - - const GLfloat vertexCoordArray[] = - { - GLfloat(target.left()) , GLfloat(vBottom), - GLfloat(target.right() + 1), GLfloat(vBottom), - GLfloat(target.left()) , GLfloat(vTop), - GLfloat(target.right() + 1), GLfloat(vTop) - }; - - const GLfloat txLeft = source.left() / m_frameSize.width(); - const GLfloat txRight = source.right() / m_frameSize.width(); - const GLfloat txTop = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? source.top() / m_frameSize.height() - : source.bottom() / m_frameSize.height(); - const GLfloat txBottom = m_scanLineDirection == QVideoSurfaceFormat::TopToBottom - ? source.bottom() / m_frameSize.height() - : source.top() / m_frameSize.height(); - - const GLfloat textureCoordArray[] = - { - txLeft , txBottom, - txRight, txBottom, - txLeft , txTop, - txRight, txTop - }; - - m_program.bind(); - - m_program.enableAttributeArray("vertexCoordArray"); - m_program.enableAttributeArray("textureCoordArray"); - m_program.setAttributeArray("vertexCoordArray", vertexCoordArray, 2); - m_program.setAttributeArray("textureCoordArray", textureCoordArray, 2); - m_program.setUniformValue("positionMatrix", positionMatrix); - - if (m_textureCount == 3) { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); - glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); - glActiveTexture(GL_TEXTURE0); - - m_program.setUniformValue("texY", 0); - m_program.setUniformValue("texU", 1); - m_program.setUniformValue("texV", 2); - } else { - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); - - m_program.setUniformValue("texRgb", 0); - } - m_program.setUniformValue("colorMatrix", m_colorMatrix); - - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - - m_program.release(); - - painter->endNativePainting(); - - return QAbstractVideoSurface::NoError; - } - - return QmlVideoSurfaceGLPainter::paint(target, painter, source); -} + 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/>. +*/ -#endif +#include "gstqtvideosinksurface.h" +#include "genericsurfacepainter.h" +#include "openglsurfacepainter.h" -/*! - \class QmlPainterVideoSurface - \internal -*/ +#include <QtCore/QCoreApplication> +#include <QtCore/QStack> +#include <QtGui/QPainter> -/*! -*/ -QmlPainterVideoSurface::QmlPainterVideoSurface(QObject *parent) - : QAbstractVideoSurface(parent) +GstQtVideoSinkSurface::GstQtVideoSinkSurface(GstQtVideoSink *sink, QObject *parent) + : QObject(parent) , m_painter(0) -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL , m_glContext(0) - , m_shaderTypes(NoShaders) - , m_shaderType(NoShaders) + , m_supportedShaderTypes(NoShaders) #endif + , m_colorsDirty(true) , m_brightness(0) , m_contrast(0) , m_hue(0) , m_saturation(0) - , m_pixelFormat(QVideoFrame::Format_Invalid) - , m_colorsDirty(true) - , m_ready(false) - , m_playing(false) + , m_forceAspectRatioDirty(true) + , m_forceAspectRatio(false) + , m_formatDirty(true) + , m_isActive(false) + , m_buffer(NULL) + , m_sink(sink) { -#if !defined(QT_NO_OPENGL) - setGLContext(const_cast<QGLContext *> (QGLContext::currentContext())); -#endif } -/*! -*/ -QmlPainterVideoSurface::~QmlPainterVideoSurface() +GstQtVideoSinkSurface::~GstQtVideoSinkSurface() { - if (isActive()) - m_painter->stop(); - - delete m_painter; + Q_ASSERT(!isActive()); + destroyPainter(); } -/*! -*/ -QList<QVideoFrame::PixelFormat> QmlPainterVideoSurface::supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const -{ - if (!m_painter) - const_cast<QmlPainterVideoSurface *>(this)->createPainter(); - - return m_painter->supportedPixelFormats(handleType); -} +//------------------------------------- -/*! -*/ -bool QmlPainterVideoSurface::isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *similar) const +QSet<BufferFormat::PixelFormat> GstQtVideoSinkSurface::supportedPixelFormats() const { - if (!m_painter) - const_cast<QmlPainterVideoSurface *>(this)->createPainter(); - - return m_painter->isFormatSupported(format, similar); -} - -/*! -*/ -bool QmlPainterVideoSurface::start(const QVideoSurfaceFormat &format) -{ - if (isActive()) - m_painter->stop(); - - if (!m_painter) - createPainter(); - - if (format.frameSize().isEmpty()) { - setError(UnsupportedFormatError); - } else { - QAbstractVideoSurface::Error error = m_painter->start(format); - - if (error != QAbstractVideoSurface::NoError) { - setError(error); - } else { - m_pixelFormat = format.pixelFormat(); - m_frameSize = format.frameSize(); - m_sourceRect = format.viewport(); - m_colorsDirty = true; - m_ready = true; - - return QAbstractVideoSurface::start(format); - } + QSet<BufferFormat::PixelFormat> result; +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + if (m_glContext) { + result += GlslSurfacePainter::supportedPixelFormats(); + result += ArbFpSurfacePainter::supportedPixelFormats(); } - - QAbstractVideoSurface::stop(); - - return false; +#endif + result += GenericSurfacePainter::supportedPixelFormats(); + return result; } -/*! -*/ -void QmlPainterVideoSurface::stop() +bool GstQtVideoSinkSurface::isActive() const { - if (isActive()) { - m_painter->stop(); - m_ready = false; - - QAbstractVideoSurface::stop(); - } + QReadLocker l(&m_isActiveLock); + return m_isActive; } -/*! -*/ -bool QmlPainterVideoSurface::present(const QVideoFrame &frame) +void GstQtVideoSinkSurface::setActive(bool active) { - if (!m_ready) { - if (!isActive()) - setError(StoppedError); - } else if (frame.isValid() - && (frame.pixelFormat() != m_pixelFormat || frame.size() != m_frameSize)) { - setError(IncorrectFormatError); - - stop(); - } else { - QAbstractVideoSurface::Error error = m_painter->setCurrentFrame(frame); - - if (error != QAbstractVideoSurface::NoError) { - setError(error); - - stop(); - } else { - m_ready = false; + GST_INFO_OBJECT(m_sink, active ? "Activating" : "Deactivating"); - emit frameChanged(); - - return true; - } + QWriteLocker l(&m_isActiveLock); + m_isActive = active; + if (!active) { + QCoreApplication::postEvent(this, new DeactivateEvent()); } - return false; } -/*! -*/ -int QmlPainterVideoSurface::brightness() const +//------------------------------------- + +int GstQtVideoSinkSurface::brightness() const { + QReadLocker l(&m_colorsLock); return m_brightness; } -/*! -*/ -void QmlPainterVideoSurface::setBrightness(int brightness) +void GstQtVideoSinkSurface::setBrightness(int brightness) { + QWriteLocker l(&m_colorsLock); m_brightness = brightness; - m_colorsDirty = true; } -/*! -*/ -int QmlPainterVideoSurface::contrast() const +int GstQtVideoSinkSurface::contrast() const { + QReadLocker l(&m_colorsLock); return m_contrast; } -/*! -*/ -void QmlPainterVideoSurface::setContrast(int contrast) +void GstQtVideoSinkSurface::setContrast(int contrast) { + QWriteLocker l(&m_colorsLock); m_contrast = contrast; - m_colorsDirty = true; } -/*! -*/ -int QmlPainterVideoSurface::hue() const +int GstQtVideoSinkSurface::hue() const { + QReadLocker l(&m_colorsLock); return m_hue; } -/*! -*/ -void QmlPainterVideoSurface::setHue(int hue) +void GstQtVideoSinkSurface::setHue(int hue) { + QWriteLocker l(&m_colorsLock); m_hue = hue; - m_colorsDirty = true; } -/*! -*/ -int QmlPainterVideoSurface::saturation() const +int GstQtVideoSinkSurface::saturation() const { + QReadLocker l(&m_colorsLock); return m_saturation; } -/*! -*/ -void QmlPainterVideoSurface::setSaturation(int saturation) +void GstQtVideoSinkSurface::setSaturation(int saturation) { + QWriteLocker l(&m_colorsLock); m_saturation = saturation; - m_colorsDirty = true; } -/*! -*/ -bool QmlPainterVideoSurface::isReady() const -{ - return m_ready; -} +//------------------------------------- -/*! -*/ -void QmlPainterVideoSurface::setReady(bool ready) +bool GstQtVideoSinkSurface::forceAspectRatio() const { - m_ready = ready; + QReadLocker l(&m_forceAspectRatioLock); + return m_forceAspectRatio; } -/*! -*/ -bool QmlPainterVideoSurface::isPlaying() const +void GstQtVideoSinkSurface::setForceAspectRatio(bool force) { - return m_playing; + QWriteLocker l(&m_forceAspectRatioLock); + if (m_forceAspectRatio != force) { + m_forceAspectRatio = force; + m_forceAspectRatioDirty = true; + } } -/*! -*/ -void QmlPainterVideoSurface::setPlaying(bool playing) -{ - m_playing = playing; -} +//------------------------------------- -/*! -*/ -void QmlPainterVideoSurface::paint(QPainter *painter, const QRectF &target, const QRectF &source) +void GstQtVideoSinkSurface::paint(QPainter *painter, int x, int y, int width, int height) { - if (!isActive() || !isPlaying()) { - painter->fillRect(target, QBrush(Qt::black)); + QRect targetArea(x, y, width, height); + + if (!isActive()) { + painter->fillRect(targetArea, Qt::black); } else { - if (m_colorsDirty) { - m_painter->updateColors(m_brightness, m_contrast, m_hue, m_saturation); - m_colorsDirty = false; + BufferFormat format = m_formatDirty ? + BufferFormat::fromCaps(GST_BUFFER_CAPS(m_buffer)) : m_bufferFormat; + + //recalculate the video area if needed + QReadLocker forceAspectRatioLocker(&m_forceAspectRatioLock); + if (targetArea != m_targetArea + || (m_formatDirty && (format.frameSize() != m_bufferFormat.frameSize() + || format.pixelAspectRatio() != m_bufferFormat.pixelAspectRatio())) + || m_forceAspectRatioDirty) + { + m_targetArea = targetArea; + m_forceAspectRatioDirty = false; + + if (m_forceAspectRatio) { + GstVideoRectangle srcRect; + srcRect.x = srcRect.y = 0; + srcRect.w = format.frameSize().width() * + format.pixelAspectRatio().width() / format.pixelAspectRatio().height(); + srcRect.h = format.frameSize().height() * + format.pixelAspectRatio().height() / format.pixelAspectRatio().width(); + + GstVideoRectangle destRect; + destRect.x = destRect.y = 0; + destRect.w = m_targetArea.width(); + destRect.h = m_targetArea.height(); + + GstVideoRectangle resultRect; + gst_video_sink_center_rect(srcRect, destRect, &resultRect, TRUE); + m_videoArea = QRect(resultRect.x, resultRect.y, resultRect.w, resultRect.h); + + //Is this really worth it, or should I paint everything black? + QRect blackArea1( + m_targetArea.left(), + m_targetArea.top(), + m_videoArea.left() == m_targetArea.left() ? + m_targetArea.width() : m_videoArea.left() - m_targetArea.left(), + m_videoArea.top() == m_targetArea.top() ? + m_targetArea.height() : m_videoArea.top() - m_targetArea.top() + ); + + QRect blackArea2( + m_videoArea.right() == m_targetArea.right() ? + m_targetArea.left() : m_videoArea.right() + 1, + m_videoArea.bottom() == m_targetArea.bottom() ? + m_targetArea.top() : m_videoArea.bottom() + 1, + m_videoArea.right() == m_targetArea.right() ? + m_targetArea.width() : m_targetArea.right() - m_videoArea.right(), + m_videoArea.bottom() == m_targetArea.bottom() ? + m_targetArea.height() : m_targetArea.bottom() - m_videoArea.bottom() + ); + + painter->fillRect(blackArea1, Qt::black); + painter->fillRect(blackArea2, Qt::black); + } else { + m_videoArea = m_targetArea; + } } + forceAspectRatioLocker.unlock(); - const QRectF sourceRect( - m_sourceRect.x() + m_sourceRect.width() * source.x(), - m_sourceRect.y() + m_sourceRect.height() * source.y(), - m_sourceRect.width() * source.width(), - m_sourceRect.height() * source.height()); + //TODO add properties for modifying clipRect - QAbstractVideoSurface::Error error = m_painter->paint(target, painter, sourceRect); + if (m_formatDirty) { + //if either pixelFormat or frameSize have changed, we need to reset the painter + //and/or change painter, in case the current one does not handle the requested format + if (format.pixelFormat() != m_bufferFormat.pixelFormat() + || format.frameSize() != m_bufferFormat.frameSize()) + { + changePainter(format); + } - if (error != QAbstractVideoSurface::NoError) { - setError(error); + //if we have a different colorspace, we need to update the colors + if (format.yCbCrColorSpace() != m_bufferFormat.yCbCrColorSpace()) { + QReadLocker l(&m_colorsLock); + m_colorsDirty = true; + } - stop(); + m_bufferFormat = format; + m_formatDirty = false; + } + + GST_TRACE_OBJECT(m_sink, + "Rendering buffer %"GST_PTR_FORMAT". " + "Frame size is (%d, %d), " + "widget size is (%d, %d), " + "calculated video area is (%d, %d)", + m_buffer, + m_bufferFormat.frameSize().width(), m_bufferFormat.frameSize().height(), + m_targetArea.width(), m_targetArea.height(), + m_videoArea.width(), m_videoArea.height()); + + if (m_painter) { + QReadLocker colorsLocker(&m_colorsLock); + if (m_colorsDirty) { + m_painter->updateColors(m_brightness, m_contrast, m_hue, m_saturation, + m_bufferFormat.yCbCrColorSpace()); + m_colorsDirty = false; + } + colorsLocker.unlock(); + + m_painter->paint(m_buffer->data, m_bufferFormat, painter, m_videoArea, m_clipRect); } } } -/*! - \fn QmlPainterVideoSurface::frameChanged() -*/ - -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL -/*! -*/ -const QGLContext *QmlPainterVideoSurface::glContext() const +QGLContext *GstQtVideoSinkSurface::glContext() const { return m_glContext; } -/*! -*/ -void QmlPainterVideoSurface::setGLContext(QGLContext *context) +void GstQtVideoSinkSurface::setGLContext(QGLContext *context) { - qDebug() << "QmlPainterVideoSurface::setGLContext context=" - << context - << " m_glContext=" << m_glContext - << " m_painter=" << m_painter; + GST_LOG_OBJECT(m_sink, "Setting GL context. context=%p m_glContext=%p m_painter=%p", + context, m_glContext, m_painter); if (m_glContext == context) return; m_glContext = context; - m_shaderTypes = NoShaders; + m_supportedShaderTypes = NoShaders; if (m_glContext) { m_glContext->makeCurrent(); const QByteArray extensions(reinterpret_cast<const char *>(glGetString(GL_EXTENSIONS))); -#ifndef QT_OPENGL_ES + GST_LOG_OBJECT(m_sink, "Available GL extensions: %s", extensions.constData()); +#ifndef QT_OPENGL_ES if (extensions.contains("ARB_fragment_program")) - m_shaderTypes |= FragmentProgramShader; + m_supportedShaderTypes |= FragmentProgramShader; #endif #ifndef QT_OPENGL_ES_2 - if (QGLShaderProgram::hasOpenGLShaderPrograms(m_glContext) && extensions.contains("ARB_shader_objects")) #endif - m_shaderTypes |= GlslShader; + m_supportedShaderTypes |= GlslShader; } - qDebug() << "QVideoSurfacePainter::setGLContext" - << " m_shaderType=" << m_shaderType - << " m_shaderTypes=" << m_shaderTypes; - - if (supportedShaderTypes() & QmlPainterVideoSurface::GlslShader) { - setShaderType(QmlPainterVideoSurface::GlslShader); - } else { - setShaderType(QmlPainterVideoSurface::FragmentProgramShader); - } + GST_LOG_OBJECT(m_sink, "Done setting GL context. m_shaderTypes=%x", (int) m_supportedShaderTypes); } -/*! - \enum QmlPainterVideoSurface::ShaderType - - \value NoShaders - \value FragmentProgramShader - \value HlslShader -*/ +#endif -/*! - \typedef QmlPainterVideoSurface::ShaderTypes -*/ +enum PainterType { Glsl, ArbFp, Generic }; -/*! -*/ -QmlPainterVideoSurface::ShaderTypes QmlPainterVideoSurface::supportedShaderTypes() const +void GstQtVideoSinkSurface::changePainter(const BufferFormat & format) { - return m_shaderTypes; -} - -/*! -*/ -QmlPainterVideoSurface::ShaderType QmlPainterVideoSurface::shaderType() const -{ - return m_shaderType; -} + if (m_painter) { + m_painter->cleanup(); + if (!m_painter->supportsFormat(format.pixelFormat())) { + delete m_painter; + m_painter = 0; + } + } -/*! -*/ -void QmlPainterVideoSurface::setShaderType(ShaderType type) -{ - qDebug() << "QmlPainterVideoSurface::setShaderType" - << " type=" << type - << " m_shaderType" << m_shaderType - << " m_shaderTypes" << m_shaderTypes; + QStack<PainterType> possiblePainters; + if (GenericSurfacePainter::supportedPixelFormats().contains(format.pixelFormat())) { + possiblePainters.push(Generic); + } - if (!(type & m_shaderTypes)) - type = NoShaders; +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + if (m_supportedShaderTypes & GstQtVideoSinkSurface::FragmentProgramShader + && ArbFpSurfacePainter::supportedPixelFormats().contains(format.pixelFormat())) { + possiblePainters.push(ArbFp); + } - if (type != m_shaderType) { - m_shaderType = type; + if (m_supportedShaderTypes & GstQtVideoSinkSurface::GlslShader + && GlslSurfacePainter::supportedPixelFormats().contains(format.pixelFormat())) { + possiblePainters.push(Glsl); + } +#endif - if (isActive()) { - m_painter->stop(); - delete m_painter; - m_painter = 0; - m_ready = false; + while (!possiblePainters.isEmpty()) { + if (!m_painter) { + PainterType type = possiblePainters.pop(); + switch(type) { +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + case Glsl: + GST_LOG_OBJECT(m_sink, "Creating GLSL painter"); + m_painter = new GlslSurfacePainter(m_glContext); + break; + case ArbFp: + GST_LOG_OBJECT(m_sink, "Creating ARB Fragment Shader painter"); + m_painter = new ArbFpSurfacePainter(m_glContext); + break; +#endif + case Generic: + GST_LOG_OBJECT(m_sink, "Creating Generic painter"); + m_painter = new GenericSurfacePainter; + break; + default: + Q_ASSERT(false); + } + } - setError(ResourceError); - QAbstractVideoSurface::stop(); - } else { + try { + m_painter->init(format); + return; + } catch (const QString & error) { + GST_WARNING_OBJECT(m_sink, "Failed to start painter: %s", error.toUtf8().constData()); delete m_painter; m_painter = 0; } - emit supportedFormatsChanged(); } - if (!m_painter) - createPainter(); + GST_ERROR_OBJECT(m_sink, "Failed to create a painter for the given format"); } -#endif - -void QmlPainterVideoSurface::viewportDestroyed() +void GstQtVideoSinkSurface::destroyPainter() { - if (m_painter) { - m_painter->viewportDestroyed(); + GST_LOG_OBJECT(m_sink, "Destroying painter"); - setError(ResourceError); - stop(); - delete m_painter; - m_painter = 0; - } + delete m_painter; + m_painter = 0; } -void QmlPainterVideoSurface::createPainter() +bool GstQtVideoSinkSurface::event(QEvent *event) { - qDebug() << "QmlPainterVideoSurface::createPainter " - << " m_glContext=" << m_glContext - << " m_painter=" << m_painter; + switch((int) event->type()) { + case BufferEventType: + { + BufferEvent *bufEvent = dynamic_cast<BufferEvent*>(event); + Q_ASSERT(bufEvent); - Q_ASSERT(!m_painter); + GST_TRACE_OBJECT(m_sink, "Received buffer %"GST_PTR_FORMAT, bufEvent->buffer); -#ifdef Q_WS_MAC - if (m_glContext) - m_glContext->makeCurrent(); + if (m_buffer) { + //free the previous buffer + gst_buffer_unref(m_buffer); + m_buffer = NULL; + } - m_painter = new QVideoSurfaceCoreGraphicsPainter(m_glContext != 0); - return; -#endif + if (isActive()) { + //schedule this frame for rendering + m_buffer = bufEvent->buffer; + m_formatDirty = bufEvent->formatDirty; + g_signal_emit(m_sink, GstQtVideoSink::s_signals[GstQtVideoSink::UPDATE_SIGNAL], 0); + } else { + //not active, drop the frame + gst_buffer_unref(bufEvent->buffer); + } -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) - switch (m_shaderType) { -#ifndef QT_OPENGL_ES - case FragmentProgramShader: - Q_ASSERT(m_glContext); - m_glContext->makeCurrent(); - m_painter = new QVideoSurfaceArbFpPainter(m_glContext); - break; -#endif - case GlslShader: - Q_ASSERT(m_glContext); - m_glContext->makeCurrent(); - m_painter = new QmlVideoSurfaceGlslPainter(m_glContext); - break; - default: - m_painter = new QmlVideoSurfaceGenericPainter; - break; + return true; } -#else - m_painter = new QmlVideoSurfaceGenericPainter; -#endif -} + case DeactivateEventType: + { + GST_LOG_OBJECT(m_sink, "Received deactivate event"); + + if (m_buffer) { + gst_buffer_unref(m_buffer); + m_buffer = NULL; + } + + m_painter->cleanup(); + destroyPainter(); -//#include "moc_qmlpaintervideosurface_p.cpp" -QT_END_NAMESPACE + g_signal_emit(m_sink, GstQtVideoSink::s_signals[GstQtVideoSink::UPDATE_SIGNAL], 0); + return true; + } + default: + return QObject::event(event); + } +} +#include "gstqtvideosinksurface.moc" diff --git a/elements/gstqtvideosink/gstqtvideosinksurface.h b/elements/gstqtvideosink/gstqtvideosinksurface.h index 2f0879c..6176444 100644 --- a/elements/gstqtvideosink/gstqtvideosinksurface.h +++ b/elements/gstqtvideosink/gstqtvideosinksurface.h @@ -1,93 +1,84 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Collabora Ltd -** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file was originally part of the Qt Mobility Components. -** -** 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. -** -****************************************************************************/ - -#ifndef QPAINTERVIDEOSURFACE_P_H -#define QPAINTERVIDEOSURFACE_P_H - -// -// 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. -// - -#include <qmobilityglobal.h> -#include <QtCore/qsize.h> -#include <QtGui/qimage.h> -#include <QtGui/qmatrix4x4.h> -#include <QtGui/qpaintengine.h> -#include <qabstractvideosurface.h> -#include <qvideoframe.h> - -QT_BEGIN_NAMESPACE -class QGLContext; -QT_END_NAMESPACE - -QT_USE_NAMESPACE +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> -QT_BEGIN_NAMESPACE - -class QmlVideoSurfacePainter -{ -public: - virtual ~QmlVideoSurfacePainter(); + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. - virtual QList<QVideoFrame::PixelFormat> supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType) const = 0; + 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. - virtual bool isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *similar) const = 0; + 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/>. +*/ - virtual QAbstractVideoSurface::Error start(const QVideoSurfaceFormat &format) = 0; - virtual void stop() = 0; +#ifndef GST_QT_VIDEO_SINK_SURFACE_H +#define GST_QT_VIDEO_SINK_SURFACE_H - virtual QAbstractVideoSurface::Error setCurrentFrame(const QVideoFrame &frame) = 0; +#include "gstqtvideosink.h" +#include "bufferformat.h" +#include "abstractsurfacepainter.h" - virtual QAbstractVideoSurface::Error paint( - const QRectF &target, QPainter *painter, const QRectF &source) = 0; +#include <QtCore/QObject> +#include <QtCore/QEvent> +#include <QtCore/QSet> +#include <QtCore/QReadWriteLock> - virtual void updateColors(int brightness, int contrast, int hue, int saturation) = 0; - virtual void viewportDestroyed() {} -}; +class QGLContext; -class QM_AUTOTEST_EXPORT QmlPainterVideoSurface : public QAbstractVideoSurface +class GstQtVideoSinkSurface : public QObject { Q_OBJECT public: - explicit QmlPainterVideoSurface(QObject *parent = 0); - ~QmlPainterVideoSurface(); + enum EventType { + BufferEventType = QEvent::User, + DeactivateEventType + }; + + //------------------------------------- + + class BufferEvent : public QEvent + { + public: + inline BufferEvent(GstBuffer *buf, bool formatDirty) + : QEvent(static_cast<QEvent::Type>(BufferEventType)), + buffer(gst_buffer_ref(buf)), + formatDirty(formatDirty) + { + } + + GstBuffer *buffer; + bool formatDirty; + }; + + //------------------------------------- + + class DeactivateEvent : public QEvent + { + public: + inline DeactivateEvent() + : QEvent(static_cast<QEvent::Type>(DeactivateEventType)) + { + } + }; - QList<QVideoFrame::PixelFormat> supportedPixelFormats( - QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const; + //------------------------------------- - bool isFormatSupported( - const QVideoSurfaceFormat &format, QVideoSurfaceFormat *similar = 0) const; + explicit GstQtVideoSinkSurface(GstQtVideoSink *sink, QObject *parent = 0); + ~GstQtVideoSinkSurface(); - bool start(const QVideoSurfaceFormat &format); - void stop(); + // API for GstQtVideoSink - bool present(const QVideoFrame &frame); + QSet<BufferFormat::PixelFormat> supportedPixelFormats() const; + + bool isActive() const; + void setActive(bool playing); + + // GstColorBalance interface int brightness() const; void setBrightness(int brightness); @@ -101,16 +92,15 @@ public: int saturation() const; void setSaturation(int saturation); - bool isReady() const; - void setReady(bool ready); + // force-aspect-ratio property - bool isPlaying() const; - void setPlaying(bool playing); + bool forceAspectRatio() const; + void setForceAspectRatio(bool force); - void paint(QPainter *painter, const QRectF &target, const QRectF &source = QRectF(0, 0, 1, 1)); + // glcontext property -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) - const QGLContext *glContext() const; +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + QGLContext *glContext() const; void setGLContext(QGLContext *context); enum ShaderType @@ -121,45 +111,60 @@ public: }; Q_DECLARE_FLAGS(ShaderTypes, ShaderType) - - ShaderTypes supportedShaderTypes() const; - - ShaderType shaderType() const; - void setShaderType(ShaderType type); #endif -public Q_SLOTS: - void viewportDestroyed(); + // paint action signal -Q_SIGNALS: - void frameChanged(); + void paint(QPainter *painter, int x, int y, int width, int height); + +protected: + bool event(QEvent *event); private: - void createPainter(); + void changePainter(const BufferFormat & format); + void destroyPainter(); + - QmlVideoSurfacePainter *m_painter; -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) + AbstractSurfacePainter *m_painter; + +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL QGLContext *m_glContext; - ShaderTypes m_shaderTypes; + ShaderTypes m_supportedShaderTypes; ShaderType m_shaderType; #endif + + // colorbalance interface properties + mutable QReadWriteLock m_colorsLock; + bool m_colorsDirty; int m_brightness; int m_contrast; int m_hue; int m_saturation; - QVideoFrame::PixelFormat m_pixelFormat; - QSize m_frameSize; - QRect m_sourceRect; - bool m_colorsDirty; - bool m_ready; - bool m_playing; -}; + // force-aspect-ratio property + mutable QReadWriteLock m_forceAspectRatioLock; + bool m_forceAspectRatioDirty; + bool m_forceAspectRatio; -#if !defined(QT_NO_OPENGL) && !defined(QT_OPENGL_ES_1_CL) && !defined(QT_OPENGL_ES_1) -Q_DECLARE_OPERATORS_FOR_FLAGS(QmlPainterVideoSurface::ShaderTypes) -#endif + // format caching + bool m_formatDirty; + BufferFormat m_bufferFormat; + QRect m_targetArea; + QRect m_videoArea; + QRect m_clipRect; -QT_END_NAMESPACE + // whether the sink is active (PAUSED or PLAYING) + mutable QReadWriteLock m_isActiveLock; + bool m_isActive; + // the buffer to be drawn next + GstBuffer *m_buffer; + + GstQtVideoSink *m_sink; +}; + +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL +Q_DECLARE_OPERATORS_FOR_FLAGS(GstQtVideoSinkSurface::ShaderTypes) #endif + +#endif // GST_QT_VIDEO_SINK_SURFACE_H diff --git a/elements/gstqtvideosink/openglsurfacepainter.cpp b/elements/gstqtvideosink/openglsurfacepainter.cpp new file mode 100644 index 0000000..e35c220 --- /dev/null +++ b/elements/gstqtvideosink/openglsurfacepainter.cpp @@ -0,0 +1,832 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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 "openglsurfacepainter.h" +#include <QtCore/qmath.h> + +#ifndef GL_TEXTURE0 +# define GL_TEXTURE0 0x84C0 +# define GL_TEXTURE1 0x84C1 +# define GL_TEXTURE2 0x84C2 +#endif +#ifndef GL_PROGRAM_ERROR_STRING_ARB +# define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#endif + +#ifndef GL_UNSIGNED_SHORT_5_6_5 +# define GL_UNSIGNED_SHORT_5_6_5 33635 +#endif + + +OpenGLSurfacePainter::OpenGLSurfacePainter(QGLContext *context) + : m_context(context) + , m_textureFormat(0) + , m_textureInternalFormat(0) + , m_textureType(0) + , m_textureCount(0) + , m_yuv(false) +{ +#ifndef QT_OPENGL_ES + glActiveTexture = (_glActiveTexture)m_context->getProcAddress(QLatin1String("glActiveTexture")); +#endif +} + +void OpenGLSurfacePainter::updateColors(int brightness, int contrast, int hue, int saturation, + BufferFormat::YCbCrColorSpace colorSpace) +{ + const qreal b = brightness / 200.0; + const qreal c = contrast / 100.0 + 1.0; + const qreal h = hue / 100.0; + const qreal s = saturation / 100.0 + 1.0; + + const qreal cosH = qCos(M_PI * h); + const qreal sinH = qSin(M_PI * h); + + const qreal h11 = 0.787 * cosH - 0.213 * sinH + 0.213; + const qreal h21 = -0.213 * cosH + 0.143 * sinH + 0.213; + const qreal h31 = -0.213 * cosH - 0.787 * sinH + 0.213; + + const qreal h12 = -0.715 * cosH - 0.715 * sinH + 0.715; + const qreal h22 = 0.285 * cosH + 0.140 * sinH + 0.715; + const qreal h32 = -0.715 * cosH + 0.715 * sinH + 0.715; + + const qreal h13 = -0.072 * cosH + 0.928 * sinH + 0.072; + const qreal h23 = -0.072 * cosH - 0.283 * sinH + 0.072; + const qreal h33 = 0.928 * cosH + 0.072 * sinH + 0.072; + + const qreal sr = (1.0 - s) * 0.3086; + const qreal sg = (1.0 - s) * 0.6094; + const qreal sb = (1.0 - s) * 0.0820; + + const qreal sr_s = sr + s; + const qreal sg_s = sg + s; + const qreal sb_s = sr + s; + + const float m4 = (s + sr + sg + sb) * (0.5 - 0.5 * c + b); + + m_colorMatrix(0, 0) = c * (sr_s * h11 + sg * h21 + sb * h31); + m_colorMatrix(0, 1) = c * (sr_s * h12 + sg * h22 + sb * h32); + m_colorMatrix(0, 2) = c * (sr_s * h13 + sg * h23 + sb * h33); + m_colorMatrix(0, 3) = m4; + + m_colorMatrix(1, 0) = c * (sr * h11 + sg_s * h21 + sb * h31); + m_colorMatrix(1, 1) = c * (sr * h12 + sg_s * h22 + sb * h32); + m_colorMatrix(1, 2) = c * (sr * h13 + sg_s * h23 + sb * h33); + m_colorMatrix(1, 3) = m4; + + m_colorMatrix(2, 0) = c * (sr * h11 + sg * h21 + sb_s * h31); + m_colorMatrix(2, 1) = c * (sr * h12 + sg * h22 + sb_s * h32); + m_colorMatrix(2, 2) = c * (sr * h13 + sg * h23 + sb_s * h33); + m_colorMatrix(2, 3) = m4; + + m_colorMatrix(3, 0) = 0.0; + m_colorMatrix(3, 1) = 0.0; + m_colorMatrix(3, 2) = 0.0; + m_colorMatrix(3, 3) = 1.0; + + if (m_yuv) { + QMatrix4x4 colorSpaceMatrix; + + switch (colorSpace) { + case BufferFormat::YCbCr_JPEG: + colorSpaceMatrix = QMatrix4x4( + 1.0, 0.000, 1.402, -0.701, + 1.0, -0.344, -0.714, 0.529, + 1.0, 1.772, 0.000, -0.886, + 0.0, 0.000, 0.000, 1.0000); + break; + case BufferFormat::YCbCr_BT709: + case BufferFormat::YCbCr_xvYCC709: + colorSpaceMatrix = QMatrix4x4( + 1.164, 0.000, 1.793, -0.5727, + 1.164, -0.534, -0.213, 0.3007, + 1.164, 2.115, 0.000, -1.1302, + 0.0, 0.000, 0.000, 1.0000); + break; + default: //BT 601: + colorSpaceMatrix = QMatrix4x4( + 1.164, 0.000, 1.596, -0.8708, + 1.164, -0.392, -0.813, 0.5296, + 1.164, 2.017, 0.000, -1.081, + 0.0, 0.000, 0.000, 1.0000); + } + + m_colorMatrix = m_colorMatrix * colorSpaceMatrix; + } +} + +void OpenGLSurfacePainter::setCurrentFrame(quint8 *data) +{ + m_context->makeCurrent(); + + for (int i = 0; i < m_textureCount; ++i) { + glBindTexture(GL_TEXTURE_2D, m_textureIds[i]); + glTexImage2D( + GL_TEXTURE_2D, + 0, + m_textureInternalFormat, + m_textureWidths[i], + m_textureHeights[i], + 0, + m_textureFormat, + m_textureType, + data + m_textureOffsets[i]); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } +} + +void OpenGLSurfacePainter::initRgbTextureInfo( + GLenum internalFormat, GLuint format, GLenum type, const QSize &size) +{ + m_yuv = false; + m_textureInternalFormat = internalFormat; + m_textureFormat = format; + m_textureType = type; + m_textureCount = 1; + m_textureWidths[0] = size.width(); + m_textureHeights[0] = size.height(); + m_textureOffsets[0] = 0; +} + +void OpenGLSurfacePainter::initYuv420PTextureInfo(const QSize &size) +{ + int bytesPerLine = (size.width() + 3) & ~3; + int bytesPerLine2 = (size.width() / 2 + 3) & ~3; + + m_yuv = true; + m_textureInternalFormat = GL_LUMINANCE; + m_textureFormat = GL_LUMINANCE; + m_textureType = GL_UNSIGNED_BYTE; + m_textureCount = 3; + m_textureWidths[0] = bytesPerLine; + m_textureHeights[0] = size.height(); + m_textureOffsets[0] = 0; + m_textureWidths[1] = bytesPerLine2; + m_textureHeights[1] = size.height() / 2; + m_textureOffsets[1] = bytesPerLine * size.height(); + m_textureWidths[2] = bytesPerLine2; + m_textureHeights[2] = size.height() / 2; + m_textureOffsets[2] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; +} + +void OpenGLSurfacePainter::initYv12TextureInfo(const QSize &size) +{ + int bytesPerLine = (size.width() + 3) & ~3; + int bytesPerLine2 = (size.width() / 2 + 3) & ~3; + + m_yuv = true; + m_textureInternalFormat = GL_LUMINANCE; + m_textureFormat = GL_LUMINANCE; + m_textureType = GL_UNSIGNED_BYTE; + m_textureCount = 3; + m_textureWidths[0] = bytesPerLine; + m_textureHeights[0] = size.height(); + m_textureOffsets[0] = 0; + m_textureWidths[1] = bytesPerLine2; + m_textureHeights[1] = size.height() / 2; + m_textureOffsets[1] = bytesPerLine * size.height() + bytesPerLine2 * size.height()/2; + m_textureWidths[2] = bytesPerLine2; + m_textureHeights[2] = size.height() / 2; + m_textureOffsets[2] = bytesPerLine * size.height(); +} + +#ifndef QT_OPENGL_ES + +# ifndef GL_FRAGMENT_PROGRAM_ARB +# define GL_FRAGMENT_PROGRAM_ARB 0x8804 +# define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +# endif + +// Paints an RGB32 frame +static const char *qt_arbfp_xrgbShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP xrgb;\n" + "TEX xrgb.xyz, fragment.texcoord[0], texture[0], 2D;\n" + "MOV xrgb.w, matrix[3].w;\n" + "DP4 result.color.x, xrgb.zyxw, matrix[0];\n" + "DP4 result.color.y, xrgb.zyxw, matrix[1];\n" + "DP4 result.color.z, xrgb.zyxw, matrix[2];\n" + "END"; + +// Paints an ARGB frame. +static const char *qt_arbfp_argbShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP argb;\n" + "TEX argb, fragment.texcoord[0], texture[0], 2D;\n" + "MOV argb.w, matrix[3].w;\n" + "DP4 result.color.x, argb.zyxw, matrix[0];\n" + "DP4 result.color.y, argb.zyxw, matrix[1];\n" + "DP4 result.color.z, argb.zyxw, matrix[2];\n" + "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" + "END"; + +// Paints an RGB(A) frame. +static const char *qt_arbfp_rgbShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP rgb;\n" + "TEX rgb, fragment.texcoord[0], texture[0], 2D;\n" + "MOV rgb.w, matrix[3].w;\n" + "DP4 result.color.x, rgb, matrix[0];\n" + "DP4 result.color.y, rgb, matrix[1];\n" + "DP4 result.color.z, rgb, matrix[2];\n" + "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" + "END"; + +// Paints a YUV420P or YV12 frame. +static const char *qt_arbfp_yuvPlanarShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP yuv;\n" + "TEX yuv.x, fragment.texcoord[0], texture[0], 2D;\n" + "TEX yuv.y, fragment.texcoord[0], texture[1], 2D;\n" + "TEX yuv.z, fragment.texcoord[0], texture[2], 2D;\n" + "MOV yuv.w, matrix[3].w;\n" + "DP4 result.color.x, yuv, matrix[0];\n" + "DP4 result.color.y, yuv, matrix[1];\n" + "DP4 result.color.z, yuv, matrix[2];\n" + "END"; + +// Paints a YUV444 frame. +static const char *qt_arbfp_xyuvShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP ayuv;\n" + "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" + "MOV ayuv.x, matrix[3].w;\n" + "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" + "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" + "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" + "END"; + +// Paints a AYUV444 frame. +static const char *qt_arbfp_ayuvShaderProgram = + "!!ARBfp1.0\n" + "PARAM matrix[4] = { program.local[0..2]," + "{ 0.0, 0.0, 0.0, 1.0 } };\n" + "TEMP ayuv;\n" + "TEX ayuv, fragment.texcoord[0], texture[0], 2D;\n" + "MOV ayuv.x, matrix[3].w;\n" + "DP4 result.color.x, ayuv.yzwx, matrix[0];\n" + "DP4 result.color.y, ayuv.yzwx, matrix[1];\n" + "DP4 result.color.z, ayuv.yzwx, matrix[2];\n" + "TEX result.color.w, fragment.texcoord[0], texture, 2D;\n" + "END"; + + + +ArbFpSurfacePainter::ArbFpSurfacePainter(QGLContext *context) + : OpenGLSurfacePainter(context) + , m_programId(0) +{ + glProgramStringARB = (_glProgramStringARB) m_context->getProcAddress( + QLatin1String("glProgramStringARB")); + glBindProgramARB = (_glBindProgramARB) m_context->getProcAddress( + QLatin1String("glBindProgramARB")); + glDeleteProgramsARB = (_glDeleteProgramsARB) m_context->getProcAddress( + QLatin1String("glDeleteProgramsARB")); + glGenProgramsARB = (_glGenProgramsARB) m_context->getProcAddress( + QLatin1String("glGenProgramsARB")); + glProgramLocalParameter4fARB = (_glProgramLocalParameter4fARB) m_context->getProcAddress( + QLatin1String("glProgramLocalParameter4fARB")); +} + +//static +QSet<BufferFormat::PixelFormat> ArbFpSurfacePainter::supportedPixelFormats() +{ + return QSet<BufferFormat::PixelFormat>() + << BufferFormat::RGB32 + << BufferFormat::BGR32 + << BufferFormat::ARGB32 + << BufferFormat::RGB24 + << BufferFormat::BGR24 + << BufferFormat::RGB565 + << BufferFormat::AYUV444 + << BufferFormat::YUV444 + << BufferFormat::YV12 + << BufferFormat::YUV420P; +} + +void ArbFpSurfacePainter::init(const BufferFormat &format) +{ + Q_ASSERT(m_textureCount == 0); + + m_context->makeCurrent(); + + const char *program = 0; + + switch (format.pixelFormat()) { + case BufferFormat::RGB32: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_xrgbShaderProgram; + break; + case BufferFormat::BGR32: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_rgbShaderProgram; + break; + case BufferFormat::ARGB32: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_argbShaderProgram; + break; + case BufferFormat::RGB24: + initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_rgbShaderProgram; + break; + case BufferFormat::BGR24: + initRgbTextureInfo(GL_RGB8, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_xrgbShaderProgram; + break; + case BufferFormat::RGB565: + initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); + program = qt_arbfp_rgbShaderProgram; + break; + case BufferFormat::YUV444: + initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_xyuvShaderProgram; + m_yuv = true; + break; + case BufferFormat::AYUV444: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + program = qt_arbfp_ayuvShaderProgram; + m_yuv = true; + break; + case BufferFormat::YV12: + initYv12TextureInfo(format.frameSize()); + program = qt_arbfp_yuvPlanarShaderProgram; + break; + case BufferFormat::YUV420P: + initYuv420PTextureInfo(format.frameSize()); + program = qt_arbfp_yuvPlanarShaderProgram; + break; + default: + Q_ASSERT(false); + break; + } + + glGenProgramsARB(1, &m_programId); + + GLenum glError = glGetError(); + if (glError != GL_NO_ERROR) { + throw QString("ARBfb Shader allocation error ") + + QString::number(static_cast<int>(glError), 16); + } else { + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); + glProgramStringARB( + GL_FRAGMENT_PROGRAM_ARB, + GL_PROGRAM_FORMAT_ASCII_ARB, + qstrlen(program), + reinterpret_cast<const GLvoid *>(program)); + + if ((glError = glGetError()) != GL_NO_ERROR) { + const GLubyte* errorString = glGetString(GL_PROGRAM_ERROR_STRING_ARB); + + glDeleteProgramsARB(1, &m_programId); + m_textureCount = 0; + m_programId = 0; + + throw QString("ARBfp Shader compile error ") + + QString::number(static_cast<int>(glError), 16) + + reinterpret_cast<const char *>(errorString); + } else { + glGenTextures(m_textureCount, m_textureIds); + } + } +} + +void ArbFpSurfacePainter::cleanup() +{ + m_context->makeCurrent(); + + glDeleteTextures(m_textureCount, m_textureIds); + glDeleteProgramsARB(1, &m_programId); + + m_textureCount = 0; + m_programId = 0; +} + +void ArbFpSurfacePainter::paint(quint8 *data, + const BufferFormat & frameFormat, + QPainter *painter, + const QRect & videoArea, + const QRect & clipRect) +{ + setCurrentFrame(data); + + bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); + bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); + + painter->beginNativePainting(); + + if (stencilTestEnabled) + glEnable(GL_STENCIL_TEST); + if (scissorTestEnabled) + glEnable(GL_SCISSOR_TEST); + + const float txLeft = clipRect.left() / frameFormat.frameSize().width(); + const float txRight = clipRect.right() / frameFormat.frameSize().width(); + const float txTop = clipRect.top() / frameFormat.frameSize().height(); + const float txBottom = clipRect.bottom() / frameFormat.frameSize().height(); + + const float tx_array[] = + { + txLeft , txBottom, + txRight, txBottom, + txLeft , txTop, + txRight, txTop + }; + + const GLfloat vTop = videoArea.top(); + const GLfloat vBottom = videoArea.bottom() + 1; + + const GLfloat v_array[] = + { + GLfloat(videoArea.left()) , GLfloat(vBottom), + GLfloat(videoArea.right() + 1), GLfloat(vBottom), + GLfloat(videoArea.left()) , GLfloat(vTop), + GLfloat(videoArea.right() + 1), GLfloat(vTop) + }; + + glEnable(GL_FRAGMENT_PROGRAM_ARB); + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, m_programId); + + glProgramLocalParameter4fARB( + GL_FRAGMENT_PROGRAM_ARB, + 0, + m_colorMatrix(0, 0), + m_colorMatrix(0, 1), + m_colorMatrix(0, 2), + m_colorMatrix(0, 3)); + glProgramLocalParameter4fARB( + GL_FRAGMENT_PROGRAM_ARB, + 1, + m_colorMatrix(1, 0), + m_colorMatrix(1, 1), + m_colorMatrix(1, 2), + m_colorMatrix(1, 3)); + glProgramLocalParameter4fARB( + GL_FRAGMENT_PROGRAM_ARB, + 2, + m_colorMatrix(2, 0), + m_colorMatrix(2, 1), + m_colorMatrix(2, 2), + m_colorMatrix(2, 3)); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); + + if (m_textureCount == 3) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); + glActiveTexture(GL_TEXTURE0); + } + + glVertexPointer(2, GL_FLOAT, 0, v_array); + glTexCoordPointer(2, GL_FLOAT, 0, tx_array); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDisable(GL_FRAGMENT_PROGRAM_ARB); + + painter->endNativePainting(); +} + +#endif + +static const char *qt_glsl_vertexShaderProgram = + "attribute highp vec4 vertexCoordArray;\n" + "attribute highp vec2 textureCoordArray;\n" + "uniform highp mat4 positionMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " gl_Position = positionMatrix * vertexCoordArray;\n" + " textureCoord = textureCoordArray;\n" + "}\n"; + +// Paints an RGB32 frame +static const char *qt_glsl_xrgbShaderProgram = + "uniform sampler2D texRgb;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" + " gl_FragColor = colorMatrix * color;\n" + "}\n"; + +// Paints an ARGB frame. +static const char *qt_glsl_argbShaderProgram = + "uniform sampler2D texRgb;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).bgr, 1.0);\n" + " color = colorMatrix * color;\n" + " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" + "}\n"; + +// Paints an RGB(A) frame. +static const char *qt_glsl_rgbShaderProgram = + "uniform sampler2D texRgb;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).rgb, 1.0);\n" + " color = colorMatrix * color;\n" + " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).a);\n" + "}\n"; + +// Paints a YUV420P or YV12 frame. +static const char *qt_glsl_yuvPlanarShaderProgram = + "uniform sampler2D texY;\n" + "uniform sampler2D texU;\n" + "uniform sampler2D texV;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(\n" + " texture2D(texY, textureCoord.st).r,\n" + " texture2D(texU, textureCoord.st).r,\n" + " texture2D(texV, textureCoord.st).r,\n" + " 1.0);\n" + " gl_FragColor = colorMatrix * color;\n" + "}\n"; + +// Paints a YUV444 frame. +static const char *qt_glsl_xyuvShaderProgram = + "uniform sampler2D texRgb;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" + " gl_FragColor = colorMatrix * color;\n" + "}\n"; + +// Paints a AYUV444 frame. +static const char *qt_glsl_ayuvShaderProgram = + "uniform sampler2D texRgb;\n" + "uniform mediump mat4 colorMatrix;\n" + "varying highp vec2 textureCoord;\n" + "void main(void)\n" + "{\n" + " highp vec4 color = vec4(texture2D(texRgb, textureCoord.st).gba, 1.0);\n" + " color = colorMatrix * color;\n" + " gl_FragColor = vec4(color.rgb, texture2D(texRgb, textureCoord.st).r);\n" + "}\n"; + + + +GlslSurfacePainter::GlslSurfacePainter(QGLContext *context) + : OpenGLSurfacePainter(context) + , m_program(context) +{ +} + +//static +QSet<BufferFormat::PixelFormat> GlslSurfacePainter::supportedPixelFormats() +{ + return QSet<BufferFormat::PixelFormat>() + << BufferFormat::YUV420P + << BufferFormat::YV12 + << BufferFormat::RGB32 + << BufferFormat::BGR32 +#if !defined(QT_OPENGL_ES) && !defined(QT_OPENGL_ES_2) + << BufferFormat::RGB24 + << BufferFormat::BGR24 + + << BufferFormat::RGB565 + << BufferFormat::AYUV444 + << BufferFormat::YUV444 +#endif + << BufferFormat::ARGB32; +} + +void GlslSurfacePainter::init(const BufferFormat &format) +{ + Q_ASSERT(m_textureCount == 0); + + m_context->makeCurrent(); + + const char *fragmentProgram = 0; + + switch (format.pixelFormat()) { + case BufferFormat::RGB32: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_xrgbShaderProgram; + break; + case BufferFormat::BGR32: + initRgbTextureInfo(GL_RGB, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_rgbShaderProgram; + break; + case BufferFormat::ARGB32: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_argbShaderProgram; + break; +#ifndef QT_OPENGL_ES + case BufferFormat::RGB24: + initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_rgbShaderProgram; + break; + case BufferFormat::BGR24: + initRgbTextureInfo(GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_argbShaderProgram; + break; +#endif + case BufferFormat::RGB565: + initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, format.frameSize()); + fragmentProgram = qt_glsl_rgbShaderProgram; + break; + case BufferFormat::YUV444: + initRgbTextureInfo(GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_xyuvShaderProgram; + m_yuv = true; + break; + case BufferFormat::AYUV444: + initRgbTextureInfo(GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, format.frameSize()); + fragmentProgram = qt_glsl_ayuvShaderProgram; + m_yuv = true; + break; + case BufferFormat::YV12: + initYv12TextureInfo(format.frameSize()); + fragmentProgram = qt_glsl_yuvPlanarShaderProgram; + break; + case BufferFormat::YUV420P: + initYuv420PTextureInfo(format.frameSize()); + fragmentProgram = qt_glsl_yuvPlanarShaderProgram; + break; + default: + Q_ASSERT(false); + break; + } + + if (!m_program.addShaderFromSourceCode(QGLShader::Vertex, qt_glsl_vertexShaderProgram)) + { + throw QString("Vertex shader compile error ") + m_program.log(); + } + else if (!m_program.addShaderFromSourceCode(QGLShader::Fragment, fragmentProgram)) + { + throw QString("Shader compile error ") + m_program.log(); + } + else if(!m_program.link()) + { + throw QString("Shader link error ") + m_program.log(); + } + else + { + glGenTextures(m_textureCount, m_textureIds); + } +} + +void GlslSurfacePainter::cleanup() +{ + m_context->makeCurrent(); + + glDeleteTextures(m_textureCount, m_textureIds); + m_program.removeAllShaders(); + + m_textureCount = 0; +} + +void GlslSurfacePainter::paint(quint8 *data, + const BufferFormat & frameFormat, + QPainter *painter, + const QRect & videoArea, + const QRect & clipRect) +{ + setCurrentFrame(data); + + bool stencilTestEnabled = glIsEnabled(GL_STENCIL_TEST); + bool scissorTestEnabled = glIsEnabled(GL_SCISSOR_TEST); + + painter->beginNativePainting(); + + if (stencilTestEnabled) + glEnable(GL_STENCIL_TEST); + if (scissorTestEnabled) + glEnable(GL_SCISSOR_TEST); + + const int width = QGLContext::currentContext()->device()->width(); + const int height = QGLContext::currentContext()->device()->height(); + + const QTransform transform = painter->deviceTransform(); + + const GLfloat wfactor = 2.0 / width; + const GLfloat hfactor = -2.0 / height; + + const GLfloat positionMatrix[4][4] = + { + { + /*(0,0)*/ GLfloat(wfactor * transform.m11() - transform.m13()), + /*(0,1)*/ GLfloat(hfactor * transform.m12() + transform.m13()), + /*(0,2)*/ 0.0, + /*(0,3)*/ GLfloat(transform.m13()) + }, { + /*(1,0)*/ GLfloat(wfactor * transform.m21() - transform.m23()), + /*(1,1)*/ GLfloat(hfactor * transform.m22() + transform.m23()), + /*(1,2)*/ 0.0, + /*(1,3)*/ GLfloat(transform.m23()) + }, { + /*(2,0)*/ 0.0, + /*(2,1)*/ 0.0, + /*(2,2)*/ -1.0, + /*(2,3)*/ 0.0 + }, { + /*(3,0)*/ GLfloat(wfactor * transform.dx() - transform.m33()), + /*(3,1)*/ GLfloat(hfactor * transform.dy() + transform.m33()), + /*(3,2)*/ 0.0, + /*(3,3)*/ GLfloat(transform.m33()) + } + }; + + const GLfloat vTop = videoArea.top(); + const GLfloat vBottom = videoArea.bottom() + 1; + + const GLfloat vertexCoordArray[] = + { + GLfloat(videoArea.left()) , GLfloat(vBottom), + GLfloat(videoArea.right() + 1), GLfloat(vBottom), + GLfloat(videoArea.left()) , GLfloat(vTop), + GLfloat(videoArea.right() + 1), GLfloat(vTop) + }; + + const GLfloat txLeft = clipRect.left() / frameFormat.frameSize().width(); + const GLfloat txRight = clipRect.right() / frameFormat.frameSize().width(); + const GLfloat txTop = clipRect.top() / frameFormat.frameSize().height(); + const GLfloat txBottom = clipRect.bottom() / frameFormat.frameSize().height(); + + const GLfloat textureCoordArray[] = + { + txLeft , txBottom, + txRight, txBottom, + txLeft , txTop, + txRight, txTop + }; + + m_program.bind(); + + m_program.enableAttributeArray("vertexCoordArray"); + m_program.enableAttributeArray("textureCoordArray"); + m_program.setAttributeArray("vertexCoordArray", vertexCoordArray, 2); + m_program.setAttributeArray("textureCoordArray", textureCoordArray, 2); + m_program.setUniformValue("positionMatrix", positionMatrix); + + if (m_textureCount == 3) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, m_textureIds[1]); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, m_textureIds[2]); + glActiveTexture(GL_TEXTURE0); + + m_program.setUniformValue("texY", 0); + m_program.setUniformValue("texU", 1); + m_program.setUniformValue("texV", 2); + } else { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_textureIds[0]); + + m_program.setUniformValue("texRgb", 0); + } + m_program.setUniformValue("colorMatrix", m_colorMatrix); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_program.release(); + + painter->endNativePainting(); +} diff --git a/elements/gstqtvideosink/openglsurfacepainter.h b/elements/gstqtvideosink/openglsurfacepainter.h new file mode 100644 index 0000000..a9cf72b --- /dev/null +++ b/elements/gstqtvideosink/openglsurfacepainter.h @@ -0,0 +1,132 @@ +/* + Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). <qt-info@nokia.com> + Copyright (C) 2011 Collabora Ltd. <info@collabora.com> + + This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 2.1 + as published by the Free Software Foundation. + + 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/>. +*/ +#ifndef OPENGLSURFACEPAINTER_H +#define OPENGLSURFACEPAINTER_H + +#ifndef GST_QT_VIDEO_SINK_NO_OPENGL + +#include "abstractsurfacepainter.h" +#include <QtOpenGL/QGLShaderProgram> + +#ifndef Q_WS_MAC +# ifndef APIENTRYP +# ifdef APIENTRY +# define APIENTRYP APIENTRY * +# else +# define APIENTRY +# define APIENTRYP * +# endif +# endif +#else +# define APIENTRY +# define APIENTRYP * +#endif + +class OpenGLSurfacePainter : public AbstractSurfacePainter +{ +public: + OpenGLSurfacePainter(QGLContext *context); + + virtual void updateColors(int brightness, int contrast, int hue, int saturation, + BufferFormat::YCbCrColorSpace colorSpace); + +protected: + void setCurrentFrame(quint8 *data); + void initRgbTextureInfo(GLenum internalFormat, GLuint format, GLenum type, const QSize &size); + void initYuv420PTextureInfo(const QSize &size); + void initYv12TextureInfo(const QSize &size); + +#ifndef QT_OPENGL_ES + typedef void (APIENTRY *_glActiveTexture) (GLenum); + _glActiveTexture glActiveTexture; +#endif + + QMatrix4x4 m_colorMatrix; + + QGLContext *m_context; + GLenum m_textureFormat; + GLuint m_textureInternalFormat; + GLenum m_textureType; + int m_textureCount; + GLuint m_textureIds[3]; + int m_textureWidths[3]; + int m_textureHeights[3]; + int m_textureOffsets[3]; + bool m_yuv; +}; + + +class ArbFpSurfacePainter : public OpenGLSurfacePainter +{ +public: + ArbFpSurfacePainter(QGLContext *context); + + static QSet<BufferFormat::PixelFormat> supportedPixelFormats(); + + virtual bool supportsFormat(BufferFormat::PixelFormat format) const { + return supportedPixelFormats().contains(format); + } + + virtual void init(const BufferFormat & format); + virtual void cleanup(); + + virtual void paint(quint8 *data, const BufferFormat & frameFormat, + QPainter *painter, const QRect & videoArea, const QRect & clipRect); + +private: + typedef void (APIENTRY *_glProgramStringARB) (GLenum, GLenum, GLsizei, const GLvoid *); + typedef void (APIENTRY *_glBindProgramARB) (GLenum, GLuint); + typedef void (APIENTRY *_glDeleteProgramsARB) (GLsizei, const GLuint *); + typedef void (APIENTRY *_glGenProgramsARB) (GLsizei, GLuint *); + typedef void (APIENTRY *_glProgramLocalParameter4fARB) ( + GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); + typedef void (APIENTRY *_glActiveTexture) (GLenum); + + _glProgramStringARB glProgramStringARB; + _glBindProgramARB glBindProgramARB; + _glDeleteProgramsARB glDeleteProgramsARB; + _glGenProgramsARB glGenProgramsARB; + _glProgramLocalParameter4fARB glProgramLocalParameter4fARB; + + GLuint m_programId; +}; + + +class GlslSurfacePainter : public OpenGLSurfacePainter +{ +public: + GlslSurfacePainter(QGLContext *context); + + static QSet<BufferFormat::PixelFormat> supportedPixelFormats(); + + virtual bool supportsFormat(BufferFormat::PixelFormat format) const { + return supportedPixelFormats().contains(format); + } + + virtual void init(const BufferFormat & format); + virtual void cleanup(); + + virtual void paint(quint8 *data, const BufferFormat & frameFormat, + QPainter *painter, const QRect & videoArea, const QRect & clipRect); + +private: + QGLShaderProgram m_program; +}; + +#endif // GST_QT_VIDEO_SINK_NO_OPENGL + +#endif // OPENGLSURFACEPAINTER_H |