diff options
Diffstat (limited to 'src/plugins/mediaservices/gstreamer/qvideosurfacegstsink.cpp')
-rw-r--r-- | src/plugins/mediaservices/gstreamer/qvideosurfacegstsink.cpp | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/src/plugins/mediaservices/gstreamer/qvideosurfacegstsink.cpp b/src/plugins/mediaservices/gstreamer/qvideosurfacegstsink.cpp new file mode 100644 index 0000000..7c5805c --- /dev/null +++ b/src/plugins/mediaservices/gstreamer/qvideosurfacegstsink.cpp @@ -0,0 +1,553 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvideosurfacegstsink.h" + +#include "qgstvideobuffer.h" + +#include <QtMultimedia/QAbstractVideoSurface> +#include <QtMultimedia/QVideoFrame> +#include <QDebug> +#include <QMap> +#include <QDebug> +#include <QThread> + +Q_DECLARE_METATYPE(QVideoSurfaceFormat) + +QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface) + : m_surface(surface) + , m_renderReturn(GST_FLOW_ERROR) + , m_bytesPerLine(0) +{ + m_supportedPixelFormats = m_surface->supportedPixelFormats(); + + connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(supportedFormatsChanged())); +} + +QList<QVideoFrame::PixelFormat> QVideoSurfaceGstDelegate::supportedPixelFormats() const +{ + QMutexLocker locker(const_cast<QMutex *>(&m_mutex)); + + return m_supportedPixelFormats; +} + +bool QVideoSurfaceGstDelegate::start(const QVideoSurfaceFormat &format, int bytesPerLine) +{ + 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); + } + + return m_started; +} + +void QVideoSurfaceGstDelegate::stop() +{ + QMutexLocker locker(&m_mutex); + + if (QThread::currentThread() == thread()) { + if (!m_surface.isNull()) + m_surface->stop(); + } else { + QMetaObject::invokeMethod(this, "queuedStop", Qt::QueuedConnection); + + m_setupCondition.wait(&m_mutex); + } + + m_started = false; +} + +GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer) +{ + QMutexLocker locker(&m_mutex); + + m_frame = QVideoFrame( + new QGstVideoBuffer(buffer, m_bytesPerLine), + 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 QVideoSurfaceGstDelegate::queuedStart() +{ + QMutexLocker locker(&m_mutex); + + m_started = m_surface->start(m_format); + + m_setupCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::queuedStop() +{ + QMutexLocker locker(&m_mutex); + + m_surface->stop(); + + m_setupCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::queuedRender() +{ + QMutexLocker locker(&m_mutex); + + if (m_surface.isNull()) { + m_renderReturn = GST_FLOW_ERROR; + } 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: + m_renderReturn = GST_FLOW_NOT_NEGOTIATED; + break; + default: + m_renderReturn = GST_FLOW_ERROR; + break; + } + } + + m_renderCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::supportedFormatsChanged() +{ + QMutexLocker locker(&m_mutex); + + m_supportedPixelFormats = m_surface->supportedPixelFormats(); +} + +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'), 8 }, + { QVideoFrame::Format_YUYV, GST_MAKE_FOURCC('Y','U','Y','V'), 8 }, + { QVideoFrame::Format_YUYV, GST_MAKE_FOURCC('Y','U','Y','2'), 8 }, + { QVideoFrame::Format_NV12, GST_MAKE_FOURCC('N','V','1','2'), 8 }, + { QVideoFrame::Format_NV21, GST_MAKE_FOURCC('N','V','2','1'), 8 } +}; + +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; + return -1; +} + +static int indexOfYuvColor(guint32 fourcc) +{ + const int count = sizeof(YuvFormat) / sizeof(YuvFormat); + + for (int i = 0; i < count; ++i) + if (qt_yuvColorLookup[i].fourcc == fourcc) + return i; + return -1; +} + +struct RgbFormat +{ + QVideoFrame::PixelFormat pixelFormat; + int bitsPerPixel; + int depth; + int endianness; + int red; + int green; + int blue; + int alpha; +}; + +static const RgbFormat qt_rgbColorLookup[] = +{ + { QVideoFrame::Format_RGB32 , 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x00000000 }, + { QVideoFrame::Format_RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 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_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; +} + +static GstVideoSinkClass *sink_parent_class; + +#define VO_SINK(s) QVideoSurfaceGstSink *sink(reinterpret_cast<QVideoSurfaceGstSink *>(s)) + +QVideoSurfaceGstSink *QVideoSurfaceGstSink::createSink(QAbstractVideoSurface *surface) +{ + QVideoSurfaceGstSink *sink = reinterpret_cast<QVideoSurfaceGstSink *>( + g_object_new(QVideoSurfaceGstSink::get_type(), 0)); + + sink->delegate = new QVideoSurfaceGstDelegate(surface); + + return sink; +} + +GType QVideoSurfaceGstSink::get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = + { + sizeof(QVideoSurfaceGstSinkClass), // class_size + base_init, // base_init + NULL, // base_finalize + class_init, // class_init + NULL, // class_finalize + NULL, // class_data + sizeof(QVideoSurfaceGstSink), // instance_size + 0, // n_preallocs + instance_init, // instance_init + 0 // value_table + }; + + type = g_type_register_static( + GST_TYPE_VIDEO_SINK, "QVideoSurfaceGstSink", &info, GTypeFlags(0)); + } + + return type; +} + +void QVideoSurfaceGstSink::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 = QVideoSurfaceGstSink::get_caps; + base_sink_class->set_caps = QVideoSurfaceGstSink::set_caps; + // base_sink_class->buffer_alloc = QVideoSurfaceGstSink::buffer_alloc; // Not implemented. + base_sink_class->start = QVideoSurfaceGstSink::start; + base_sink_class->stop = QVideoSurfaceGstSink::stop; + // base_sink_class->unlock = QVideoSurfaceGstSink::unlock; // Not implemented. + // base_sink_class->event = QVideoSurfaceGstSink::event; // Not implemented. + base_sink_class->preroll = QVideoSurfaceGstSink::preroll; + base_sink_class->render = QVideoSurfaceGstSink::render; + + GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class); + element_class->change_state = QVideoSurfaceGstSink::change_state; + + GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class); + object_class->finalize = QVideoSurfaceGstSink::finalize; +} + +void QVideoSurfaceGstSink::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 QVideoSurfaceGstSink::instance_init(GTypeInstance *instance, gpointer g_class) +{ + VO_SINK(instance); + + Q_UNUSED(g_class); + + sink->delegate = 0; +} + +void QVideoSurfaceGstSink::finalize(GObject *object) +{ + Q_UNUSED(object); +} + +GstStateChangeReturn QVideoSurfaceGstSink::change_state( + GstElement *element, GstStateChange transition) +{ + Q_UNUSED(element); + + return GST_ELEMENT_CLASS(sink_parent_class)->change_state( + element, transition); +} + +GstCaps *QVideoSurfaceGstSink::get_caps(GstBaseSink *base) +{ + VO_SINK(base); + + GstCaps *caps = gst_caps_new_empty(); + + foreach (QVideoFrame::PixelFormat format, sink->delegate->supportedPixelFormats()) { + int index = indexOfYuvColor(format); + + if (index != -1) { + gst_caps_append_structure(caps, 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)); + 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); + } + } + } + + return caps; +} + +gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps) +{ + VO_SINK(base); + + if (!caps) { + sink->delegate->stop(); + + return TRUE; + } else { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + + //qDebug() << gst_caps_to_string(caps); + + 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); + } + + int bytesPerLine = ((size.width() * bitsPerPixel / 8) + 3) & ~3; + + if (sink->delegate->start(format, bytesPerLine)) + return TRUE; + } + + } + + return FALSE; +} + +GstFlowReturn QVideoSurfaceGstSink::buffer_alloc( + GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer) +{ + // Should implement this when the buffer pool situation is sorted. + Q_UNUSED(base); + Q_UNUSED(offset); + Q_UNUSED(size); + Q_UNUSED(caps); + Q_UNUSED(buffer); + + return GST_FLOW_NOT_SUPPORTED; +} + +gboolean QVideoSurfaceGstSink::start(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::stop(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::unlock(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event) +{ + Q_UNUSED(base); + Q_UNUSED(event); + + return TRUE; +} + +GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + + return sink->delegate->render(buffer); +} + +GstFlowReturn QVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + return sink->delegate->render(buffer); +} + |