GStreamer: port to 1.0.

0.10 is still used by default.
To enable GStreamer 1.0, pass GST_VERSION=1.0 to qmake
for qtmultimedia.pro.

Contributions from:
Andrew den Exter <andrew.den.exter@qinetic.com.au>
Ilya Smelykh <ilya@videoexpertsgroup.com>
Jim Hodapp <jim.hodapp@canonical.com>
Sergio Schvezov <sergio.schvezov@canonical.com>

Change-Id: I72a46d1170a8794a149bdb5e20767afcc5b7587c
Reviewed-by: Andrew den Exter <andrew.den.exter@qinetic.com.au>
This commit is contained in:
Yoann Lopes
2014-11-20 17:54:18 +01:00
committed by Andrew den Exter
parent 7e3d69668e
commit 108dda7a90
71 changed files with 3669 additions and 1382 deletions

View File

@@ -2,6 +2,7 @@ TEMPLATE = lib
TARGET = qgsttools_p
QPRO_PWD = $$PWD
QT = core-private multimedia-private gui-private
!static:DEFINES += QT_MAKEDLL
@@ -15,15 +16,17 @@ LIBS_PRIVATE += \
CONFIG += link_pkgconfig
PKGCONFIG_PRIVATE += \
gstreamer-0.10 \
gstreamer-base-0.10 \
gstreamer-interfaces-0.10 \
gstreamer-audio-0.10 \
gstreamer-video-0.10 \
gstreamer-pbutils-0.10
PKGCONFIG += \
gstreamer-$$GST_VERSION \
gstreamer-base-$$GST_VERSION \
gstreamer-audio-$$GST_VERSION \
gstreamer-video-$$GST_VERSION \
gstreamer-pbutils-$$GST_VERSION
maemo*: PKGCONFIG_PRIVATE +=gstreamer-plugins-bad-0.10
equals(GST_VERSION,"0.10") {
PKGCONFIG_PRIVATE += gstreamer-interfaces-0.10
maemo*: PKGCONFIG_PRIVATE +=gstreamer-plugins-bad-0.10
}
config_resourcepolicy {
DEFINES += HAVE_RESOURCE_POLICY
@@ -33,38 +36,36 @@ config_resourcepolicy {
# Header files must go inside source directory of a module
# to be installed by syncqt.
INCLUDEPATH += ../multimedia/gsttools_headers/
INCLUDEPATH += ../plugins/gstreamer/mediaplayer/
VPATH += ../multimedia/gsttools_headers/
PRIVATE_HEADERS += \
qgstbufferpoolinterface_p.h \
qgstreamerbushelper_p.h \
qgstreamermessage_p.h \
qgstutils_p.h \
qgstvideobuffer_p.h \
qvideosurfacegstsink_p.h \
qgstreamerbufferprobe_p.h \
qgstreamervideorendererinterface_p.h \
qgstreameraudioinputselector_p.h \
qgstreamervideorenderer_p.h \
qgstreamervideoinputdevicecontrol_p.h \
gstvideoconnector_p.h \
qgstcodecsinfo_p.h \
qgstreamervideoprobecontrol_p.h \
qgstreameraudioprobecontrol_p.h \
qgstreamervideowindow_p.h
SOURCES += \
qgstbufferpoolinterface.cpp \
qgstreamerbushelper.cpp \
qgstreamermessage.cpp \
qgstutils.cpp \
qgstvideobuffer.cpp \
qvideosurfacegstsink.cpp \
qgstreamerbufferprobe.cpp \
qgstreamervideorendererinterface.cpp \
qgstreameraudioinputselector.cpp \
qgstreamervideorenderer.cpp \
qgstreamervideoinputdevicecontrol.cpp \
qgstcodecsinfo.cpp \
gstvideoconnector.c \
qgstreamervideoprobecontrol.cpp \
qgstreameraudioprobecontrol.cpp \
qgstreamervideowindow.cpp
@@ -79,25 +80,54 @@ qtHaveModule(widgets) {
qgstreamervideowidget.cpp
}
maemo6 {
PKGCONFIG_PRIVATE += qmsystem2
equals(GST_VERSION,"0.10") {
PRIVATE_HEADERS += \
qgstbufferpoolinterface_p.h \
gstvideoconnector_p.h \
contains(QT_CONFIG, opengles2):qtHaveModule(widgets) {
PRIVATE_HEADERS += qgstreamergltexturerenderer_p.h
SOURCES += qgstreamergltexturerenderer.cpp
QT += opengl
LIBS_PRIVATE += -lEGL -lgstmeegointerfaces-0.10
SOURCES += \
qgstbufferpoolinterface.cpp \
qvideosurfacegstsink.cpp \
gstvideoconnector.c
maemo6 {
PKGCONFIG_PRIVATE += qmsystem2
contains(QT_CONFIG, opengles2):qtHaveModule(widgets) {
PRIVATE_HEADERS += qgstreamergltexturerenderer_p.h
SOURCES += qgstreamergltexturerenderer.cpp
QT += opengl
LIBS_PRIVATE += -lEGL -lgstmeegointerfaces-0.10
}
}
} else {
PRIVATE_HEADERS += \
qgstvideorendererplugin_p.h \
qgstvideorenderersink_p.h
SOURCES += \
qgstvideorendererplugin.cpp \
qgstvideorenderersink.cpp
}
mir: {
contains(QT_CONFIG, opengles2):qtHaveModule(widgets) {
PRIVATE_HEADERS += qgstreamermirtexturerenderer_p.h
SOURCES += qgstreamermirtexturerenderer.cpp
QT += opengl quick
LIBS += -lEGL
}
DEFINES += HAVE_MIR
}
config_gstreamer_appsrc {
PKGCONFIG_PRIVATE += gstreamer-app-0.10
PKGCONFIG_PRIVATE += gstreamer-app-$$GST_VERSION
PRIVATE_HEADERS += qgstappsrc_p.h
SOURCES += qgstappsrc.cpp
DEFINES += HAVE_GST_APPSRC
LIBS_PRIVATE += -lgstapp-0.10
LIBS_PRIVATE += -lgstapp-$$GST_VERSION
}
config_linux_v4l: DEFINES += USE_V4L

View File

@@ -147,23 +147,44 @@ void QGstAppSrc::pushDataToAppSrc()
size = qMin(m_stream->bytesAvailable(), (qint64)m_dataRequestSize);
if (size) {
void *data = g_malloc(size);
GstBuffer* buffer = gst_app_buffer_new(data, size, g_free, data);
GstBuffer* buffer = gst_buffer_new_and_alloc(size);
#if GST_CHECK_VERSION(1,0,0)
GstMapInfo mapInfo;
gst_buffer_map(buffer, &mapInfo, GST_MAP_WRITE);
void* bufferData = mapInfo.data;
#else
void* bufferData = GST_BUFFER_DATA(buffer);
#endif
buffer->offset = m_stream->pos();
qint64 bytesRead = m_stream->read((char*)GST_BUFFER_DATA(buffer), size);
qint64 bytesRead = m_stream->read((char*)bufferData, size);
buffer->offset_end = buffer->offset + bytesRead - 1;
#if GST_CHECK_VERSION(1,0,0)
gst_buffer_unmap(buffer, &mapInfo);
#endif
if (bytesRead > 0) {
m_dataRequested = false;
m_enoughData = false;
GstFlowReturn ret = gst_app_src_push_buffer (GST_APP_SRC (element()), buffer);
if (ret == GST_FLOW_ERROR) {
qWarning()<<"appsrc: push buffer error";
#if GST_CHECK_VERSION(1,0,0)
} else if (ret == GST_FLOW_FLUSHING) {
qWarning()<<"appsrc: push buffer wrong state";
}
#else
} else if (ret == GST_FLOW_WRONG_STATE) {
qWarning()<<"appsrc: push buffer wrong state";
} else if (ret == GST_FLOW_RESEND) {
}
#endif
#if GST_VERSION_MAJOR < 1
else if (ret == GST_FLOW_RESEND) {
qWarning()<<"appsrc: push buffer resend";
}
#endif
}
} else {
sendEOS();

View File

@@ -32,7 +32,7 @@
****************************************************************************/
#include "qgstcodecsinfo_p.h"
#include "qgstutils_p.h"
#include <QtCore/qset.h>
#ifdef QMEDIA_GSTREAMER_CAMERABIN
@@ -146,7 +146,7 @@ GstCaps* QGstCodecsInfo::supportedElementCaps(GstElementFactoryListType elementT
if (fakeEncoderMimeTypes.contains(gst_structure_get_name(structure)))
continue;
GstStructure *newStructure = gst_structure_new(gst_structure_get_name(structure), NULL);
GstStructure *newStructure = qt_gst_structure_new_empty(gst_structure_get_name(structure));
//add structure fields to distinguish between formats with similar mime types,
//like audio/mpeg
@@ -166,7 +166,11 @@ GstCaps* QGstCodecsInfo::supportedElementCaps(GstElementFactoryListType elementT
}
}
#if GST_CHECK_VERSION(1,0,0)
res =
#endif
gst_caps_merge_structure(res, newStructure);
}
gst_caps_unref(caps);
}

View File

@@ -37,32 +37,48 @@
QGstreamerAudioProbeControl::QGstreamerAudioProbeControl(QObject *parent)
: QMediaAudioProbeControl(parent)
{
}
QGstreamerAudioProbeControl::~QGstreamerAudioProbeControl()
{
}
void QGstreamerAudioProbeControl::bufferProbed(GstBuffer* buffer)
void QGstreamerAudioProbeControl::probeCaps(GstCaps *caps)
{
GstCaps* caps = gst_buffer_get_caps(buffer);
if (!caps)
return;
QAudioFormat format = QGstUtils::audioFormatForCaps(caps);
gst_caps_unref(caps);
if (!format.isValid())
return;
QAudioBuffer audioBuffer = QAudioBuffer(QByteArray((const char*)buffer->data, buffer->size), format);
QMutexLocker locker(&m_bufferMutex);
m_format = format;
}
{
QMutexLocker locker(&m_bufferMutex);
m_pendingBuffer = audioBuffer;
QMetaObject::invokeMethod(this, "bufferProbed", Qt::QueuedConnection);
bool QGstreamerAudioProbeControl::probeBuffer(GstBuffer *buffer)
{
qint64 position = GST_BUFFER_TIMESTAMP(buffer);
position = position >= 0
? position / G_GINT64_CONSTANT(1000) // microseconds
: -1;
QByteArray data;
#if GST_CHECK_VERSION(1,0,0)
GstMapInfo info;
if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
data = QByteArray(reinterpret_cast<const char *>(info.data), info.size);
gst_buffer_unmap(buffer, &info);
} else {
return true;
}
#else
data = QByteArray(reinterpret_cast<const char *>(buffer->data), buffer->size);
#endif
QMutexLocker locker(&m_bufferMutex);
if (m_format.isValid()) {
if (!m_pendingBuffer.isValid())
QMetaObject::invokeMethod(this, "bufferProbed", Qt::QueuedConnection);
m_pendingBuffer = QAudioBuffer(data, m_format, position);
}
return true;
}
void QGstreamerAudioProbeControl::bufferProbed()
@@ -73,6 +89,7 @@ void QGstreamerAudioProbeControl::bufferProbed()
if (!m_pendingBuffer.isValid())
return;
audioBuffer = m_pendingBuffer;
m_pendingBuffer = QAudioBuffer();
}
emit audioBufferProbed(audioBuffer);
}

View File

@@ -0,0 +1,174 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgstreamerbufferprobe_p.h"
#include "qgstutils_p.h"
QT_BEGIN_NAMESPACE
QGstreamerBufferProbe::QGstreamerBufferProbe(Flags flags)
#if GST_CHECK_VERSION(1,0,0)
: m_capsProbeId(-1)
#else
: m_caps(0)
#endif
, m_bufferProbeId(-1)
, m_flags(flags)
{
}
QGstreamerBufferProbe::~QGstreamerBufferProbe()
{
#if !GST_CHECK_VERSION(1,0,0)
if (m_caps)
gst_caps_unref(m_caps);
#endif
}
void QGstreamerBufferProbe::addProbeToPad(GstPad *pad, bool downstream)
{
if (GstCaps *caps = qt_gst_pad_get_current_caps(pad)) {
probeCaps(caps);
gst_caps_unref(caps);
}
#if GST_CHECK_VERSION(1,0,0)
if (m_flags & ProbeCaps) {
m_capsProbeId = gst_pad_add_probe(
pad,
downstream
? GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM
: GST_PAD_PROBE_TYPE_EVENT_UPSTREAM,
capsProbe,
this,
NULL);
}
if (m_flags & ProbeBuffers) {
m_bufferProbeId = gst_pad_add_probe(
pad, GST_PAD_PROBE_TYPE_BUFFER, bufferProbe, this, NULL);
}
#else
Q_UNUSED(downstream);
m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(bufferProbe), this);
#endif
}
void QGstreamerBufferProbe::removeProbeFromPad(GstPad *pad)
{
#if GST_CHECK_VERSION(1,0,0)
if (m_capsProbeId != -1) {
gst_pad_remove_probe(pad, m_capsProbeId);
m_capsProbeId = -1;
}
if (m_bufferProbeId != -1) {
gst_pad_remove_probe(pad, m_bufferProbeId);
m_bufferProbeId = -1;
}
#else
if (m_bufferProbeId != -1) {
gst_pad_remove_buffer_probe(pad, m_bufferProbeId);
m_bufferProbeId = -1;
if (m_caps) {
gst_caps_unref(m_caps);
m_caps = 0;
}
}
#endif
}
void QGstreamerBufferProbe::probeCaps(GstCaps *)
{
}
bool QGstreamerBufferProbe::probeBuffer(GstBuffer *)
{
return true;
}
#if GST_CHECK_VERSION(1,0,0)
GstPadProbeReturn QGstreamerBufferProbe::capsProbe(
GstPad *, GstPadProbeInfo *info, gpointer user_data)
{
QGstreamerBufferProbe * const control = static_cast<QGstreamerBufferProbe *>(user_data);
if (GstEvent * const event = gst_pad_probe_info_get_event(info)) {
if (GST_EVENT_TYPE(event) == GST_EVENT_CAPS) {
GstCaps *caps;
gst_event_parse_caps(event, &caps);
control->probeCaps(caps);
}
}
return GST_PAD_PROBE_OK;
}
GstPadProbeReturn QGstreamerBufferProbe::bufferProbe(
GstPad *, GstPadProbeInfo *info, gpointer user_data)
{
QGstreamerBufferProbe * const control = static_cast<QGstreamerBufferProbe *>(user_data);
if (GstBuffer * const buffer = gst_pad_probe_info_get_buffer(info))
return control->probeBuffer(buffer) ? GST_PAD_PROBE_OK : GST_PAD_PROBE_DROP;
return GST_PAD_PROBE_OK;
}
#else
gboolean QGstreamerBufferProbe::bufferProbe(GstElement *, GstBuffer *buffer, gpointer user_data)
{
QGstreamerBufferProbe * const control = static_cast<QGstreamerBufferProbe *>(user_data);
if (control->m_flags & ProbeCaps) {
GstCaps *caps = gst_buffer_get_caps(buffer);
if (caps && (!control->m_caps || !gst_caps_is_equal(control->m_caps, caps))) {
qSwap(caps, control->m_caps);
control->probeCaps(control->m_caps);
}
if (caps)
gst_caps_unref(caps);
}
if (control->m_flags & ProbeBuffers) {
return control->probeBuffer(buffer) ? TRUE : FALSE;
} else {
return TRUE;
}
}
#endif
QT_END_NAMESPACE

View File

@@ -154,13 +154,21 @@ QGstreamerBusHelper::QGstreamerBusHelper(GstBus* bus, QObject* parent):
QObject(parent)
{
d = new QGstreamerBusHelperPrivate(this, bus);
#if GST_CHECK_VERSION(1,0,0)
gst_bus_set_sync_handler(bus, (GstBusSyncHandler)syncGstBusFilter, d, 0);
#else
gst_bus_set_sync_handler(bus, (GstBusSyncHandler)syncGstBusFilter, d);
#endif
gst_object_ref(GST_OBJECT(bus));
}
QGstreamerBusHelper::~QGstreamerBusHelper()
{
#if GST_CHECK_VERSION(1,0,0)
gst_bus_set_sync_handler(d->bus(), 0, 0, 0);
#else
gst_bus_set_sync_handler(d->bus(),0,0);
#endif
gst_object_unref(GST_OBJECT(d->bus()));
}

View File

@@ -0,0 +1,351 @@
/****************************************************************************
**
** Copyright (C) 2014 Canonical Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgstreamermirtexturerenderer_p.h"
#include <qgstreamerplayersession.h>
#include <private/qvideosurfacegstsink_p.h>
#include <private/qgstutils_p.h>
#include <qabstractvideosurface.h>
#include <QAbstractVideoBuffer>
#include <QGuiApplication>
#include <QDebug>
#include <QtQuick/QQuickWindow>
#include <QOpenGLContext>
#include <QGLContext>
#include <QGuiApplication>
#include <qgl.h>
#include <gst/gst.h>
static QGstreamerMirTextureRenderer *rendererInstance = NULL;
class QGstreamerMirTextureBuffer : public QAbstractVideoBuffer
{
public:
QGstreamerMirTextureBuffer(GLuint textureId) :
QAbstractVideoBuffer(QAbstractVideoBuffer::GLTextureHandle),
m_textureId(textureId)
{
}
MapMode mapMode() const { return NotMapped; }
uchar *map(MapMode mode, int *numBytes, int *bytesPerLine)
{
qDebug() << Q_FUNC_INFO;
Q_UNUSED(mode);
Q_UNUSED(numBytes);
Q_UNUSED(bytesPerLine);
return NULL;
}
void unmap() { qDebug() << Q_FUNC_INFO; }
QVariant handle() const { return QVariant::fromValue<unsigned int>(m_textureId); }
GLuint textureId() { return m_textureId; }
private:
GLuint m_textureId;
};
QGstreamerMirTextureRenderer::QGstreamerMirTextureRenderer(QObject *parent
, const QGstreamerPlayerSession *playerSession)
: QVideoRendererControl(0), m_videoSink(0), m_surface(0),
m_glSurface(0),
m_context(0),
m_glContext(0),
m_textureId(0),
m_offscreenSurface(0),
m_textureBuffer(0)
{
Q_UNUSED(parent);
setPlayerSession(playerSession);
}
QGstreamerMirTextureRenderer::~QGstreamerMirTextureRenderer()
{
if (m_videoSink)
gst_object_unref(GST_OBJECT(m_videoSink));
delete m_glContext;
delete m_offscreenSurface;
}
GstElement *QGstreamerMirTextureRenderer::videoSink()
{
qDebug() << Q_FUNC_INFO;
// FIXME: Ugly hack until I figure out why passing this segfaults in the g_signal handler
rendererInstance = const_cast<QGstreamerMirTextureRenderer*>(this);
if (!m_videoSink && m_surface) {
qDebug() << Q_FUNC_INFO << ": using mirsink, (this: " << this << ")";
m_videoSink = gst_element_factory_make("mirsink", "video-output");
connect(QGuiApplication::instance(), SIGNAL(focusWindowChanged(QWindow*)),
this, SLOT(handleFocusWindowChanged(QWindow*)), Qt::QueuedConnection);
g_signal_connect(G_OBJECT(m_videoSink), "frame-ready", G_CALLBACK(handleFrameReady),
(gpointer)this);
}
if (m_videoSink) {
gst_object_ref_sink(GST_OBJECT(m_videoSink));
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER,
padBufferProbe, this, NULL);
}
return m_videoSink;
}
QWindow *QGstreamerMirTextureRenderer::createOffscreenWindow(const QSurfaceFormat &format)
{
QWindow *w = new QWindow();
w->setSurfaceType(QWindow::OpenGLSurface);
w->setFormat(format);
w->setGeometry(0, 0, 1, 1);
w->setFlags(w->flags() | Qt::WindowTransparentForInput);
w->create();
return w;
}
void QGstreamerMirTextureRenderer::handleFrameReady(gpointer userData)
{
QGstreamerMirTextureRenderer *renderer = reinterpret_cast<QGstreamerMirTextureRenderer*>(userData);
#if 1
QMutexLocker locker(&rendererInstance->m_mutex);
QMetaObject::invokeMethod(rendererInstance, "renderFrame", Qt::QueuedConnection);
#else
// FIXME!
//QMutexLocker locker(&renderer->m_mutex);
QMetaObject::invokeMethod(renderer, "renderFrame", Qt::QueuedConnection);
#endif
}
void QGstreamerMirTextureRenderer::renderFrame()
{
//qDebug() << Q_FUNC_INFO;
if (m_context)
m_context->makeCurrent();
GstState pendingState = GST_STATE_NULL;
GstState newState = GST_STATE_NULL;
// Don't block and return immediately:
GstStateChangeReturn ret = gst_element_get_state(m_videoSink, &newState,
&pendingState, 0);
if (ret == GST_STATE_CHANGE_FAILURE || newState == GST_STATE_NULL||
pendingState == GST_STATE_NULL) {
qWarning() << "Invalid state change for renderer, aborting";
stopRenderer();
return;
}
if (!m_surface->isActive()) {
qDebug() << "m_surface is not active";
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
GstCaps *caps = gst_pad_get_current_caps(pad);
if (caps) {
// Get the native video size from the video sink
QSize newNativeSize = QGstUtils::capsCorrectedResolution(caps);
if (m_nativeSize != newNativeSize) {
m_nativeSize = newNativeSize;
emit nativeSizeChanged();
}
gst_caps_unref(caps);
}
// Start the surface
QVideoSurfaceFormat format(m_nativeSize, QVideoFrame::Format_RGB32, QAbstractVideoBuffer::GLTextureHandle);
qDebug() << "m_nativeSize: " << m_nativeSize;
qDebug() << "format: " << format;
if (!m_surface->start(format)) {
qWarning() << Q_FUNC_INFO << ": failed to start the video surface " << format;
return;
}
}
QGstreamerMirTextureBuffer *buffer = new QGstreamerMirTextureBuffer(m_textureId);
//qDebug() << "frameSize: " << m_surface->surfaceFormat().frameSize();
QVideoFrame frame(buffer, m_surface->surfaceFormat().frameSize(),
m_surface->surfaceFormat().pixelFormat());
frame.setMetaData("TextureId", m_textureId);
// Display the video frame on the surface:
m_surface->present(frame);
}
GstPadProbeReturn QGstreamerMirTextureRenderer::padBufferProbe(GstPad *pad, GstPadProbeInfo *info, gpointer userData)
{
Q_UNUSED(pad);
Q_UNUSED(info);
QGstreamerMirTextureRenderer *control = reinterpret_cast<QGstreamerMirTextureRenderer*>(userData);
QMetaObject::invokeMethod(control, "updateNativeVideoSize", Qt::QueuedConnection);
return GST_PAD_PROBE_REMOVE;
}
void QGstreamerMirTextureRenderer::stopRenderer()
{
if (m_surface)
m_surface->stop();
}
QAbstractVideoSurface *QGstreamerMirTextureRenderer::surface() const
{
return m_surface;
}
void QGstreamerMirTextureRenderer::setSurface(QAbstractVideoSurface *surface)
{
qDebug() << Q_FUNC_INFO;
if (m_surface != surface) {
qDebug() << "Saving current QGLContext";
m_context = const_cast<QGLContext*>(QGLContext::currentContext());
if (m_videoSink)
gst_object_unref(GST_OBJECT(m_videoSink));
m_videoSink = 0;
if (m_surface) {
disconnect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
this, SLOT(handleFormatChange()));
}
bool wasReady = isReady();
m_surface = surface;
if (m_surface) {
connect(m_surface.data(), SIGNAL(supportedFormatsChanged()),
this, SLOT(handleFormatChange()));
}
if (wasReady != isReady())
emit readyChanged(isReady());
emit sinkChanged();
}
}
void QGstreamerMirTextureRenderer::setPlayerSession(const QGstreamerPlayerSession *playerSession)
{
m_playerSession = const_cast<QGstreamerPlayerSession*>(playerSession);
}
void QGstreamerMirTextureRenderer::handleFormatChange()
{
qDebug() << "Supported formats list has changed, reload video output";
if (m_videoSink)
gst_object_unref(GST_OBJECT(m_videoSink));
m_videoSink = 0;
emit sinkChanged();
}
void QGstreamerMirTextureRenderer::updateNativeVideoSize()
{
//qDebug() << Q_FUNC_INFO;
const QSize oldSize = m_nativeSize;
if (m_videoSink) {
// Find video native size to update video widget size hint
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
GstCaps *caps = gst_pad_get_current_caps(pad);
if (caps) {
m_nativeSize = QGstUtils::capsCorrectedResolution(caps);
gst_caps_unref(caps);
}
} else {
m_nativeSize = QSize();
}
qDebug() << Q_FUNC_INFO << oldSize << m_nativeSize << m_videoSink;
if (m_nativeSize != oldSize)
emit nativeSizeChanged();
}
void QGstreamerMirTextureRenderer::handleFocusWindowChanged(QWindow *window)
{
qDebug() << Q_FUNC_INFO;
QOpenGLContext *currContext = QOpenGLContext::currentContext();
QQuickWindow *w = dynamic_cast<QQuickWindow*>(window);
// If we don't have a GL context in the current thread, create one and share it
// with the render thread GL context
if (!currContext && !m_glContext) {
// This emulates the new QOffscreenWindow class with Qt5.1
m_offscreenSurface = createOffscreenWindow(w->openglContext()->surface()->format());
m_offscreenSurface->setParent(window);
QOpenGLContext *shareContext = 0;
if (m_surface)
shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>());
m_glContext = new QOpenGLContext;
m_glContext->setFormat(m_offscreenSurface->requestedFormat());
if (shareContext)
m_glContext->setShareContext(shareContext);
if (!m_glContext->create())
{
qWarning() << "Failed to create new shared context.";
return;
}
}
if (m_glContext)
m_glContext->makeCurrent(m_offscreenSurface);
if (m_textureId == 0) {
glGenTextures(1, &m_textureId);
qDebug() << "texture_id (handleFocusWindowChanged): " << m_textureId << endl;
g_object_set(G_OBJECT(m_videoSink), "texture-id", m_textureId, (char*)NULL);
}
}

View File

@@ -32,7 +32,8 @@
****************************************************************************/
#include "qgstreamervideoprobecontrol_p.h"
#include <private/qvideosurfacegstsink_p.h>
#include "qgstutils_p.h"
#include <private/qgstvideobuffer_p.h>
QGstreamerVideoProbeControl::QGstreamerVideoProbeControl(QObject *parent)
@@ -40,12 +41,10 @@ QGstreamerVideoProbeControl::QGstreamerVideoProbeControl(QObject *parent)
, m_flushing(false)
, m_frameProbed(false)
{
}
QGstreamerVideoProbeControl::~QGstreamerVideoProbeControl()
{
}
void QGstreamerVideoProbeControl::startFlushing()
@@ -67,33 +66,49 @@ void QGstreamerVideoProbeControl::stopFlushing()
m_flushing = false;
}
void QGstreamerVideoProbeControl::bufferProbed(GstBuffer* buffer)
void QGstreamerVideoProbeControl::probeCaps(GstCaps *caps)
{
if (m_flushing)
return;
GstCaps* caps = gst_buffer_get_caps(buffer);
if (!caps)
return;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo videoInfo;
QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &videoInfo);
QMutexLocker locker(&m_frameMutex);
m_videoInfo = videoInfo;
#else
int bytesPerLine = 0;
QVideoSurfaceFormat format = QVideoSurfaceGstSink::formatForCaps(caps, &bytesPerLine);
gst_caps_unref(caps);
if (!format.isValid() || !bytesPerLine)
return;
QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &bytesPerLine);
QVideoFrame frame = QVideoFrame(new QGstVideoBuffer(buffer, bytesPerLine),
format.frameSize(), format.pixelFormat());
QMutexLocker locker(&m_frameMutex);
m_bytesPerLine = bytesPerLine;
#endif
m_format = format;
}
QVideoSurfaceGstSink::setFrameTimeStamps(&frame, buffer);
bool QGstreamerVideoProbeControl::probeBuffer(GstBuffer *buffer)
{
QMutexLocker locker(&m_frameMutex);
if (m_flushing || !m_format.isValid())
return true;
QVideoFrame frame(
#if GST_CHECK_VERSION(1,0,0)
new QGstVideoBuffer(buffer, m_videoInfo),
#else
new QGstVideoBuffer(buffer, m_bytesPerLine),
#endif
m_format.frameSize(),
m_format.pixelFormat());
QGstUtils::setFrameTimeStamps(&frame, buffer);
m_frameProbed = true;
{
QMutexLocker locker(&m_frameMutex);
m_pendingFrame = frame;
if (!m_pendingFrame.isValid())
QMetaObject::invokeMethod(this, "frameProbed", Qt::QueuedConnection);
}
m_pendingFrame = frame;
return true;
}
void QGstreamerVideoProbeControl::frameProbed()
@@ -104,6 +119,7 @@ void QGstreamerVideoProbeControl::frameProbed()
if (!m_pendingFrame.isValid())
return;
frame = m_pendingFrame;
m_pendingFrame = QVideoFrame();
}
emit videoFrameProbed(frame);
}

View File

@@ -35,8 +35,7 @@
#include <private/qvideosurfacegstsink_p.h>
#include <private/qgstutils_p.h>
#include <qabstractvideosurface.h>
#include <QDebug>
#include <QtCore/qdebug.h>
#include <gst/gst.h>

View File

@@ -40,8 +40,13 @@
#include <QtGui/qpainter.h>
#include <gst/gst.h>
#if !GST_CHECK_VERSION(1,0,0)
#include <gst/interfaces/xoverlay.h>
#include <gst/interfaces/propertyprobe.h>
#else
#include <gst/video/videooverlay.h>
#endif
QT_BEGIN_NAMESPACE
@@ -130,8 +135,6 @@ void QGstreamerVideoWidgetControl::createVideoWidget()
m_videoSink = gst_element_factory_make ("ximagesink", NULL);
qt_gst_object_ref_sink(GST_OBJECT (m_videoSink)); //Take ownership
}
GstElement *QGstreamerVideoWidgetControl::videoSink()
@@ -169,9 +172,13 @@ bool QGstreamerVideoWidgetControl::processSyncMessage(const QGstreamerMessage &m
{
GstMessage* gm = message.rawMessage();
#if !GST_CHECK_VERSION(1,0,0)
if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(gm->structure, "prepare-xwindow-id")) {
#else
if (gm && (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(gst_message_get_structure(gm), "prepare-window-handle")) {
#endif
setOverlay();
QMetaObject::invokeMethod(this, "updateNativeVideoSize", Qt::QueuedConnection);
return true;
@@ -199,17 +206,24 @@ bool QGstreamerVideoWidgetControl::processBusMessage(const QGstreamerMessage &me
void QGstreamerVideoWidgetControl::setOverlay()
{
#if !GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_windowId);
}
#else
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), m_windowId);
}
#endif
}
void QGstreamerVideoWidgetControl::updateNativeVideoSize()
{
if (m_videoSink) {
//find video native size to update video widget size hint
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
GstCaps *caps = gst_pad_get_negotiated_caps(pad);
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
GstCaps *caps = qt_gst_pad_get_current_caps(pad);
gst_object_unref(GST_OBJECT(pad));
if (caps) {
@@ -225,8 +239,13 @@ void QGstreamerVideoWidgetControl::updateNativeVideoSize()
void QGstreamerVideoWidgetControl::windowExposed()
{
#if !GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink))
gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink));
#else
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink))
gst_video_overlay_expose(GST_VIDEO_OVERLAY(m_videoSink));
#endif
}
QWidget *QGstreamerVideoWidgetControl::videoWidget()

View File

@@ -37,36 +37,49 @@
#include <QtCore/qdebug.h>
#include <gst/gst.h>
#if !GST_CHECK_VERSION(1,0,0)
#include <gst/interfaces/xoverlay.h>
#include <gst/interfaces/propertyprobe.h>
#else
#include <gst/video/videooverlay.h>
#endif
QGstreamerVideoWindow::QGstreamerVideoWindow(QObject *parent, const char *elementName)
: QVideoWindowControl(parent)
, QGstreamerBufferProbe(QGstreamerBufferProbe::ProbeCaps)
, m_videoSink(0)
, m_windowId(0)
, m_aspectRatioMode(Qt::KeepAspectRatio)
, m_fullScreen(false)
, m_colorKey(QColor::Invalid)
{
if (elementName)
if (elementName) {
m_videoSink = gst_element_factory_make(elementName, NULL);
else
} else {
m_videoSink = gst_element_factory_make("xvimagesink", NULL);
}
if (m_videoSink) {
qt_gst_object_ref_sink(GST_OBJECT(m_videoSink)); //Take ownership
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padBufferProbe), this);
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
addProbeToPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
else
qDebug() << "No m_videoSink available!";
}
QGstreamerVideoWindow::~QGstreamerVideoWindow()
{
if (m_videoSink)
if (m_videoSink) {
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
removeProbeFromPad(pad);
gst_object_unref(GST_OBJECT(pad));
gst_object_unref(GST_OBJECT(m_videoSink));
}
}
WId QGstreamerVideoWindow::winId() const
@@ -82,11 +95,15 @@ void QGstreamerVideoWindow::setWinId(WId id)
WId oldId = m_windowId;
m_windowId = id;
#if GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), m_windowId);
}
#else
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_windowId);
}
#endif
if (!oldId)
emit readyChanged(true);
@@ -97,20 +114,26 @@ void QGstreamerVideoWindow::setWinId(WId id)
bool QGstreamerVideoWindow::processSyncMessage(const QGstreamerMessage &message)
{
GstMessage* gm = message.rawMessage();
#if GST_CHECK_VERSION(1,0,0)
const GstStructure *s = gst_message_get_structure(gm);
if ((GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(s, "prepare-window-handle") &&
m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
gst_video_overlay_set_window_handle(GST_VIDEO_OVERLAY(m_videoSink), m_windowId);
return true;
}
#else
if ((GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) &&
gst_structure_has_name(gm->structure, "prepare-xwindow-id") &&
m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_windowId);
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padBufferProbe), this);
gst_object_unref(GST_OBJECT(pad));
return true;
}
#endif
return false;
}
@@ -122,7 +145,19 @@ QRect QGstreamerVideoWindow::displayRect() const
void QGstreamerVideoWindow::setDisplayRect(const QRect &rect)
{
m_displayRect = rect;
#if GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
if (m_displayRect.isEmpty())
gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(m_videoSink), -1, -1, -1, -1);
else
gst_video_overlay_set_render_rectangle(GST_VIDEO_OVERLAY(m_videoSink),
m_displayRect.x(),
m_displayRect.y(),
m_displayRect.width(),
m_displayRect.height());
repaint();
}
#else
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
#if GST_VERSION_MICRO >= 29
if (m_displayRect.isEmpty())
@@ -136,6 +171,7 @@ void QGstreamerVideoWindow::setDisplayRect(const QRect &rect)
repaint();
#endif
}
#endif
}
Qt::AspectRatioMode QGstreamerVideoWindow::aspectRatioMode() const
@@ -157,6 +193,16 @@ void QGstreamerVideoWindow::setAspectRatioMode(Qt::AspectRatioMode mode)
void QGstreamerVideoWindow::repaint()
{
#if GST_CHECK_VERSION(1,0,0)
if (m_videoSink && GST_IS_VIDEO_OVERLAY(m_videoSink)) {
//don't call gst_x_overlay_expose if the sink is in null state
GstState state = GST_STATE_NULL;
GstStateChangeReturn res = gst_element_get_state(m_videoSink, &state, NULL, 1000000);
if (res != GST_STATE_CHANGE_FAILURE && state != GST_STATE_NULL) {
gst_video_overlay_expose(GST_VIDEO_OVERLAY(m_videoSink));
}
}
#else
if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) {
//don't call gst_x_overlay_expose if the sink is in null state
GstState state = GST_STATE_NULL;
@@ -165,6 +211,7 @@ void QGstreamerVideoWindow::repaint()
gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink));
}
}
#endif
}
QColor QGstreamerVideoWindow::colorKey() const
@@ -296,32 +343,22 @@ QSize QGstreamerVideoWindow::nativeSize() const
return m_nativeSize;
}
void QGstreamerVideoWindow::padBufferProbe(GstPad *pad, GstBuffer * /* buffer */, gpointer user_data)
void QGstreamerVideoWindow::probeCaps(GstCaps *caps)
{
QGstreamerVideoWindow *control = reinterpret_cast<QGstreamerVideoWindow*>(user_data);
QMetaObject::invokeMethod(control, "updateNativeVideoSize", Qt::QueuedConnection);
gst_pad_remove_buffer_probe(pad, control->m_bufferProbeId);
QSize resolution = QGstUtils::capsCorrectedResolution(caps);
QMetaObject::invokeMethod(
this,
"updateNativeVideoSize",
Qt::QueuedConnection,
Q_ARG(QSize, resolution));
}
void QGstreamerVideoWindow::updateNativeVideoSize()
void QGstreamerVideoWindow::updateNativeVideoSize(const QSize &size)
{
const QSize oldSize = m_nativeSize;
m_nativeSize = QSize();
if (m_videoSink) {
//find video native size to update video widget size hint
GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink");
GstCaps *caps = gst_pad_get_negotiated_caps(pad);
gst_object_unref(GST_OBJECT(pad));
if (caps) {
m_nativeSize = QGstUtils::capsCorrectedResolution(caps);
gst_caps_unref(caps);
}
}
if (m_nativeSize != oldSize)
if (m_nativeSize != size) {
m_nativeSize = size;
emit nativeSizeChanged();
}
}
GstElement *QGstreamerVideoWindow::videoSink()

View File

@@ -40,7 +40,14 @@
#include <QtCore/qsize.h>
#include <QtCore/qset.h>
#include <QtCore/qstringlist.h>
#include <QtGui/qimage.h>
#include <qaudioformat.h>
#include <QtMultimedia/qvideosurfaceformat.h>
#include <gst/audio/audio.h>
#include <gst/video/video.h>
template<typename T, int N> static int lengthOf(const T (&)[N]) { return N; }
#ifdef USE_V4L
# include <private/qcore_unix_p.h>
@@ -82,15 +89,24 @@ static void addTagToMap(const GstTagList *list,
map->insert(QByteArray(tag), g_value_get_boolean(&val));
break;
case G_TYPE_CHAR:
#if GLIB_CHECK_VERSION(2,32,0)
map->insert(QByteArray(tag), g_value_get_schar(&val));
#else
map->insert(QByteArray(tag), g_value_get_char(&val));
#endif
break;
case G_TYPE_DOUBLE:
map->insert(QByteArray(tag), g_value_get_double(&val));
break;
default:
// GST_TYPE_DATE is a function, not a constant, so pull it out of the switch
#if GST_CHECK_VERSION(1,0,0)
if (G_VALUE_TYPE(&val) == G_TYPE_DATE) {
const GDate *date = (const GDate *)g_value_get_boxed(&val);
#else
if (G_VALUE_TYPE(&val) == GST_TYPE_DATE) {
const GDate *date = gst_value_get_date(&val);
#endif
if (g_date_valid(date)) {
int year = g_date_get_year(date);
int month = g_date_get_month(date);
@@ -169,6 +185,42 @@ QSize QGstUtils::capsCorrectedResolution(const GstCaps *caps)
return size;
}
#if GST_CHECK_VERSION(1,0,0)
namespace {
struct AudioFormat
{
GstAudioFormat format;
QAudioFormat::SampleType sampleType;
QAudioFormat::Endian byteOrder;
int sampleSize;
};
static const AudioFormat qt_audioLookup[] =
{
{ GST_AUDIO_FORMAT_S8 , QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 8 },
{ GST_AUDIO_FORMAT_U8 , QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 8 },
{ GST_AUDIO_FORMAT_S16LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 16 },
{ GST_AUDIO_FORMAT_S16BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 16 },
{ GST_AUDIO_FORMAT_U16LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 16 },
{ GST_AUDIO_FORMAT_U16BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 16 },
{ GST_AUDIO_FORMAT_S32LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 32 },
{ GST_AUDIO_FORMAT_S32BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 32 },
{ GST_AUDIO_FORMAT_U32LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 32 },
{ GST_AUDIO_FORMAT_U32BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 32 },
{ GST_AUDIO_FORMAT_S24LE, QAudioFormat::SignedInt , QAudioFormat::LittleEndian, 24 },
{ GST_AUDIO_FORMAT_S24BE, QAudioFormat::SignedInt , QAudioFormat::BigEndian , 24 },
{ GST_AUDIO_FORMAT_U24LE, QAudioFormat::UnSignedInt, QAudioFormat::LittleEndian, 24 },
{ GST_AUDIO_FORMAT_U24BE, QAudioFormat::UnSignedInt, QAudioFormat::BigEndian , 24 },
{ GST_AUDIO_FORMAT_F32LE, QAudioFormat::Float , QAudioFormat::LittleEndian, 32 },
{ GST_AUDIO_FORMAT_F32BE, QAudioFormat::Float , QAudioFormat::BigEndian , 32 },
{ GST_AUDIO_FORMAT_F64LE, QAudioFormat::Float , QAudioFormat::LittleEndian, 64 },
{ GST_AUDIO_FORMAT_F64BE, QAudioFormat::Float , QAudioFormat::BigEndian , 64 }
};
}
#endif
/*!
Returns audio format for caps.
If caps doesn't have a valid audio format, an empty QAudioFormat is returned.
@@ -176,9 +228,26 @@ QSize QGstUtils::capsCorrectedResolution(const GstCaps *caps)
QAudioFormat QGstUtils::audioFormatForCaps(const GstCaps *caps)
{
const GstStructure *structure = gst_caps_get_structure(caps, 0);
QAudioFormat format;
#if GST_CHECK_VERSION(1,0,0)
GstAudioInfo info;
if (gst_audio_info_from_caps(&info, caps)) {
for (int i = 0; i < lengthOf(qt_audioLookup); ++i) {
if (qt_audioLookup[i].format != info.finfo->format)
continue;
format.setSampleType(qt_audioLookup[i].sampleType);
format.setByteOrder(qt_audioLookup[i].byteOrder);
format.setSampleSize(qt_audioLookup[i].sampleSize);
format.setSampleRate(info.rate);
format.setChannelCount(info.channels);
format.setCodec(QStringLiteral("audio/pcm"));
return format;
}
}
#else
const GstStructure *structure = gst_caps_get_structure(caps, 0);
if (qstrcmp(gst_structure_get_name(structure), "audio/x-raw-int") == 0) {
@@ -249,16 +318,28 @@ QAudioFormat QGstUtils::audioFormatForCaps(const GstCaps *caps)
} else {
return QAudioFormat();
}
#endif
return format;
}
#if GST_CHECK_VERSION(1,0,0)
/*!
Returns audio format for a sample.
If the buffer doesn't have a valid audio format, an empty QAudioFormat is returned.
*/
QAudioFormat QGstUtils::audioFormatForSample(GstSample *sample)
{
GstCaps* caps = gst_sample_get_caps(sample);
if (!caps)
return QAudioFormat();
return QGstUtils::audioFormatForCaps(caps);
}
#else
/*!
Returns audio format for a buffer.
If the buffer doesn't have a valid audio format, an empty QAudioFormat is returned.
*/
QAudioFormat QGstUtils::audioFormatForBuffer(GstBuffer *buffer)
{
GstCaps* caps = gst_buffer_get_caps(buffer);
@@ -269,7 +350,7 @@ QAudioFormat QGstUtils::audioFormatForBuffer(GstBuffer *buffer)
gst_caps_unref(caps);
return format;
}
#endif
/*!
Builds GstCaps for an audio format.
@@ -277,8 +358,32 @@ QAudioFormat QGstUtils::audioFormatForBuffer(GstBuffer *buffer)
Caller must unref GstCaps.
*/
GstCaps *QGstUtils::capsForAudioFormat(QAudioFormat format)
GstCaps *QGstUtils::capsForAudioFormat(const QAudioFormat &format)
{
if (!format.isValid())
return 0;
#if GST_CHECK_VERSION(1,0,0)
const QAudioFormat::SampleType sampleType = format.sampleType();
const QAudioFormat::Endian byteOrder = format.byteOrder();
const int sampleSize = format.sampleSize();
for (int i = 0; i < lengthOf(qt_audioLookup); ++i) {
if (qt_audioLookup[i].sampleType != sampleType
|| qt_audioLookup[i].byteOrder != byteOrder
|| qt_audioLookup[i].sampleSize != sampleSize) {
continue;
}
return gst_caps_new_simple(
"audio/x-raw",
"format" , G_TYPE_STRING, gst_audio_format_to_string(qt_audioLookup[i].format),
"rate" , G_TYPE_INT , format.sampleRate(),
"channels", G_TYPE_INT , format.channelCount(),
NULL);
}
return 0;
#else
GstStructure *structure = 0;
if (format.isValid()) {
@@ -313,6 +418,7 @@ GstCaps *QGstUtils::capsForAudioFormat(QAudioFormat format)
}
return caps;
#endif
}
void QGstUtils::initializeGst()
@@ -576,10 +682,629 @@ QByteArray QGstUtils::cameraDriver(const QString &device, GstElementFactory *fac
return QByteArray();
}
QSet<QString> QGstUtils::supportedMimeTypes(bool (*isValidFactory)(GstElementFactory *factory))
{
QSet<QString> supportedMimeTypes;
//enumerate supported mime types
gst_init(NULL, NULL);
#if GST_CHECK_VERSION(1,0,0)
GstRegistry *registry = gst_registry_get();
GList *orig_plugins = gst_registry_get_plugin_list(registry);
#else
GstRegistry *registry = gst_registry_get_default();
GList *orig_plugins = gst_default_registry_get_plugin_list ();
#endif
for (GList *plugins = orig_plugins; plugins; plugins = g_list_next(plugins)) {
GstPlugin *plugin = (GstPlugin *) (plugins->data);
#if GST_CHECK_VERSION(1,0,0)
if (GST_OBJECT_FLAG_IS_SET(GST_OBJECT(plugin), GST_PLUGIN_FLAG_BLACKLISTED))
continue;
#else
if (plugin->flags & (1<<1)) //GST_PLUGIN_FLAG_BLACKLISTED
continue;
#endif
GList *orig_features = gst_registry_get_feature_list_by_plugin(
registry, gst_plugin_get_name(plugin));
for (GList *features = orig_features; features; features = g_list_next(features)) {
if (G_UNLIKELY(features->data == NULL))
continue;
GstPluginFeature *feature = GST_PLUGIN_FEATURE(features->data);
GstElementFactory *factory;
if (GST_IS_TYPE_FIND_FACTORY(feature)) {
QString name(gst_plugin_feature_get_name(feature));
if (name.contains('/')) //filter out any string without '/' which is obviously not a mime type
supportedMimeTypes.insert(name.toLower());
continue;
} else if (!GST_IS_ELEMENT_FACTORY (feature)
|| !(factory = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature)))) {
continue;
} else if (!isValidFactory(factory)) {
// Do nothing
} else for (const GList *pads = gst_element_factory_get_static_pad_templates(factory);
pads;
pads = g_list_next(pads)) {
GstStaticPadTemplate *padtemplate = static_cast<GstStaticPadTemplate *>(pads->data);
if (padtemplate->direction == GST_PAD_SINK && padtemplate->static_caps.string) {
GstCaps *caps = gst_static_caps_get(&padtemplate->static_caps);
if (gst_caps_is_any(caps) || gst_caps_is_empty(caps)) {
} else for (guint i = 0; i < gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
QString nameLowcase = QString(gst_structure_get_name(structure)).toLower();
supportedMimeTypes.insert(nameLowcase);
if (nameLowcase.contains("mpeg")) {
//Because mpeg version number is only included in the detail
//description, it is necessary to manually extract this information
//in order to match the mime type of mpeg4.
const GValue *value = gst_structure_get_value(structure, "mpegversion");
if (value) {
gchar *str = gst_value_serialize(value);
QString versions(str);
QStringList elements = versions.split(QRegExp("\\D+"), QString::SkipEmptyParts);
foreach (const QString &e, elements)
supportedMimeTypes.insert(nameLowcase + e);
g_free(str);
}
}
}
}
}
gst_object_unref(factory);
}
gst_plugin_feature_list_free(orig_features);
}
gst_plugin_list_free (orig_plugins);
#if defined QT_SUPPORTEDMIMETYPES_DEBUG
QStringList list = supportedMimeTypes.toList();
list.sort();
if (qgetenv("QT_DEBUG_PLUGINS").toInt() > 0) {
foreach (const QString &type, list)
qDebug() << type;
}
#endif
return supportedMimeTypes;
}
namespace {
struct ColorFormat { QImage::Format imageFormat; GstVideoFormat gstFormat; };
static const ColorFormat qt_colorLookup[] =
{
{ QImage::Format_RGBX8888, GST_VIDEO_FORMAT_RGBx },
{ QImage::Format_RGBA8888, GST_VIDEO_FORMAT_RGBA },
{ QImage::Format_RGB888 , GST_VIDEO_FORMAT_RGB },
{ QImage::Format_RGB16 , GST_VIDEO_FORMAT_RGB16 }
};
}
#if GST_CHECK_VERSION(1,0,0)
QImage QGstUtils::bufferToImage(GstBuffer *buffer, const GstVideoInfo &videoInfo)
#else
QImage QGstUtils::bufferToImage(GstBuffer *buffer)
#endif
{
QImage img;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo info = videoInfo;
GstVideoFrame frame;
if (!gst_video_frame_map(&frame, &info, buffer, GST_MAP_READ))
return img;
#else
GstCaps *caps = gst_buffer_get_caps(buffer);
if (!caps)
return img;
GstStructure *structure = gst_caps_get_structure (caps, 0);
gint width = 0;
gint height = 0;
if (!structure
|| !gst_structure_get_int(structure, "width", &width)
|| !gst_structure_get_int(structure, "height", &height)
|| width <= 0
|| height <= 0) {
gst_caps_unref(caps);
return img;
}
gst_caps_unref(caps);
#endif
#if GST_CHECK_VERSION(1,0,0)
if (videoInfo.finfo->format == GST_VIDEO_FORMAT_I420) {
const int width = videoInfo.width;
const int height = videoInfo.height;
const int stride[] = { frame.info.stride[0], frame.info.stride[1], frame.info.stride[2] };
const uchar *data[] = {
static_cast<const uchar *>(frame.data[0]),
static_cast<const uchar *>(frame.data[1]),
static_cast<const uchar *>(frame.data[2])
};
#else
if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-yuv") == 0) {
const int stride[] = { width, width / 2, width / 2 };
const uchar *data[] = {
(const uchar *)buffer->data,
(const uchar *)buffer->data + width * height,
(const uchar *)buffer->data + width * height * 5 / 4
};
#endif
img = QImage(width/2, height/2, QImage::Format_RGB32);
for (int y=0; y<height; y+=2) {
const uchar *yLine = data[0] + (y * stride[0]);
const uchar *uLine = data[1] + (y * stride[1] / 2);
const uchar *vLine = data[2] + (y * stride[2] / 2);
for (int x=0; x<width; x+=2) {
const qreal Y = 1.164*(yLine[x]-16);
const int U = uLine[x/2]-128;
const int V = vLine[x/2]-128;
int b = qBound(0, int(Y + 2.018*U), 255);
int g = qBound(0, int(Y - 0.813*V - 0.391*U), 255);
int r = qBound(0, int(Y + 1.596*V), 255);
img.setPixel(x/2,y/2,qRgb(r,g,b));
}
}
#if GST_CHECK_VERSION(1,0,0)
} else for (int i = 0; i < lengthOf(qt_colorLookup); ++i) {
if (qt_colorLookup[i].gstFormat != videoInfo.finfo->format)
continue;
const QImage image(
static_cast<const uchar *>(frame.data[0]),
videoInfo.width,
videoInfo.height,
frame.info.stride[0],
qt_colorLookup[i].imageFormat);
img = image;
img.detach();
break;
}
gst_video_frame_unmap(&frame);
#else
} else if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) {
QImage::Format format = QImage::Format_Invalid;
int bpp = 0;
gst_structure_get_int(structure, "bpp", &bpp);
if (bpp == 24)
format = QImage::Format_RGB888;
else if (bpp == 32)
format = QImage::Format_RGB32;
if (format != QImage::Format_Invalid) {
img = QImage((const uchar *)buffer->data,
width,
height,
format);
img.bits(); //detach
}
}
#endif
return img;
}
namespace {
#if GST_CHECK_VERSION(1,0,0)
struct VideoFormat
{
QVideoFrame::PixelFormat pixelFormat;
GstVideoFormat gstFormat;
};
static const VideoFormat qt_videoFormatLookup[] =
{
{ QVideoFrame::Format_YUV420P, GST_VIDEO_FORMAT_I420 },
{ QVideoFrame::Format_YV12 , GST_VIDEO_FORMAT_YV12 },
{ QVideoFrame::Format_UYVY , GST_VIDEO_FORMAT_UYVY },
{ QVideoFrame::Format_YUYV , GST_VIDEO_FORMAT_YUY2 },
{ QVideoFrame::Format_NV12 , GST_VIDEO_FORMAT_NV12 },
{ QVideoFrame::Format_NV21 , GST_VIDEO_FORMAT_NV21 },
{ QVideoFrame::Format_AYUV444, GST_VIDEO_FORMAT_AYUV },
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
{ QVideoFrame::Format_RGB32 , GST_VIDEO_FORMAT_BGRx },
{ QVideoFrame::Format_BGR32 , GST_VIDEO_FORMAT_RGBx },
{ QVideoFrame::Format_ARGB32, GST_VIDEO_FORMAT_BGRA },
{ QVideoFrame::Format_BGRA32, GST_VIDEO_FORMAT_ARGB },
#else
{ QVideoFrame::Format_RGB32 , GST_VIDEO_FORMAT_xRGB },
{ QVideoFrame::Format_BGR32 , GST_VIDEO_FORMAT_xBGR },
{ QVideoFrame::Format_ARGB32, GST_VIDEO_FORMAT_ARGB },
{ QVideoFrame::Format_BGRA32, GST_VIDEO_FORMAT_BGRA },
#endif
{ QVideoFrame::Format_RGB24 , GST_VIDEO_FORMAT_RGB },
{ QVideoFrame::Format_BGR24 , GST_VIDEO_FORMAT_BGR },
{ QVideoFrame::Format_RGB565, GST_VIDEO_FORMAT_RGB16 }
};
static int indexOfVideoFormat(QVideoFrame::PixelFormat format)
{
for (int i = 0; i < lengthOf(qt_videoFormatLookup); ++i)
if (qt_videoFormatLookup[i].pixelFormat == format)
return i;
return -1;
}
static int indexOfVideoFormat(GstVideoFormat format)
{
for (int i = 0; i < lengthOf(qt_videoFormatLookup); ++i)
if (qt_videoFormatLookup[i].gstFormat == format)
return i;
return -1;
}
#else
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;
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;
}
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, int(0xFF000000), 0x00000000 },
{ QVideoFrame::Format_RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 },
{ QVideoFrame::Format_BGR32 , 32, 24, 4321, int(0xFF000000), 0x00FF0000, 0x0000FF00, 0x00000000 },
{ QVideoFrame::Format_BGR32 , 32, 24, 1234, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 },
{ QVideoFrame::Format_ARGB32, 32, 24, 4321, 0x0000FF00, 0x00FF0000, int(0xFF000000), 0x000000FF },
{ QVideoFrame::Format_ARGB32, 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, int(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;
}
#endif
}
#if GST_CHECK_VERSION(1,0,0)
QVideoSurfaceFormat QGstUtils::formatForCaps(
GstCaps *caps, GstVideoInfo *info, QAbstractVideoBuffer::HandleType handleType)
{
if (gst_video_info_from_caps(info, caps)) {
int index = indexOfVideoFormat(info->finfo->format);
if (index != -1) {
QVideoSurfaceFormat format(
QSize(info->width, info->height),
qt_videoFormatLookup[index].pixelFormat,
handleType);
if (info->fps_d > 0)
format.setFrameRate(qreal(info->fps_d) / info->fps_n);
if (info->par_d > 0)
format.setPixelAspectRatio(info->par_n, info->par_d);
return format;
}
}
return QVideoSurfaceFormat();
}
#else
QVideoSurfaceFormat QGstUtils::formatForCaps(
GstCaps *caps, int *bytesPerLine, QAbstractVideoBuffer::HandleType handleType)
{
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, handleType);
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();
}
#endif
GstCaps *QGstUtils::capsForFormats(const QList<QVideoFrame::PixelFormat> &formats)
{
GstCaps *caps = gst_caps_new_empty();
#if GST_CHECK_VERSION(1,0,0)
foreach (QVideoFrame::PixelFormat format, formats) {
int index = indexOfVideoFormat(format);
if (index != -1) {
gst_caps_append_structure(caps, gst_structure_new(
"video/x-raw",
"format" , G_TYPE_STRING, gst_video_format_to_string(qt_videoFormatLookup[index].gstFormat),
NULL));
}
}
#else
foreach (QVideoFrame::PixelFormat format, formats) {
int index = indexOfYuvColor(format);
if (index != -1) {
gst_caps_append_structure(caps, gst_structure_new(
"video/x-raw-yuv",
"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",
"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);
}
}
}
#endif
gst_caps_set_simple(
caps,
"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,
NULL);
return caps;
}
void QGstUtils::setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer)
{
// GStreamer uses nanoseconds, Qt uses microseconds
qint64 startTime = GST_BUFFER_TIMESTAMP(buffer);
if (startTime >= 0) {
frame->setStartTime(startTime/G_GINT64_CONSTANT (1000));
qint64 duration = GST_BUFFER_DURATION(buffer);
if (duration >= 0)
frame->setEndTime((startTime + duration)/G_GINT64_CONSTANT (1000));
}
}
void QGstUtils::setMetaData(GstElement *element, const QMap<QByteArray, QVariant> &data)
{
if (!GST_IS_TAG_SETTER(element))
return;
gst_tag_setter_reset_tags(GST_TAG_SETTER(element));
QMapIterator<QByteArray, QVariant> it(data);
while (it.hasNext()) {
it.next();
const QString tagName = it.key();
const QVariant tagValue = it.value();
switch (tagValue.type()) {
case QVariant::String:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toString().toUtf8().constData(),
NULL);
break;
case QVariant::Int:
case QVariant::LongLong:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toInt(),
NULL);
break;
case QVariant::Double:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toDouble(),
NULL);
break;
case QVariant::DateTime: {
QDateTime date = tagValue.toDateTime().toLocalTime();
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
gst_date_time_new_local_time(
date.date().year(), date.date().month(), date.date().day(),
date.time().hour(), date.time().minute(), date.time().second()),
NULL);
break;
}
default:
break;
}
}
}
void QGstUtils::setMetaData(GstBin *bin, const QMap<QByteArray, QVariant> &data)
{
GstIterator *elements = gst_bin_iterate_all_by_interface(bin, GST_TYPE_TAG_SETTER);
#if GST_CHECK_VERSION(1,0,0)
GValue item = G_VALUE_INIT;
while (gst_iterator_next(elements, &item) == GST_ITERATOR_OK) {
GstElement * const element = GST_ELEMENT(g_value_get_object(&item));
#else
GstElement *element = 0;
while (gst_iterator_next(elements, (void**)&element) == GST_ITERATOR_OK) {
#endif
setMetaData(element, data);
}
gst_iterator_free(elements);
}
GstCaps *QGstUtils::videoFilterCaps()
{
static GstStaticCaps staticCaps = GST_STATIC_CAPS(
#if GST_CHECK_VERSION(1,2,0)
"video/x-raw(ANY);"
#elif GST_CHECK_VERSION(1,0,0)
"video/x-raw;"
#else
"video/x-raw-yuv;"
"video/x-raw-rgb;"
"video/x-raw-data;"
"video/x-android-buffer;"
#endif
"image/jpeg;"
"video/x-h264");
return gst_caps_make_writable(gst_static_caps_get(&staticCaps));
}
void qt_gst_object_ref_sink(gpointer object)
{
#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 24)
#if GST_CHECK_VERSION(0,10,24)
gst_object_ref_sink(object);
#else
g_return_if_fail (GST_IS_OBJECT(object));
@@ -595,4 +1320,50 @@ void qt_gst_object_ref_sink(gpointer object)
#endif
}
GstCaps *qt_gst_pad_get_current_caps(GstPad *pad)
{
#if GST_CHECK_VERSION(1,0,0)
return gst_pad_get_current_caps(pad);
#else
return gst_pad_get_negotiated_caps(pad);
#endif
}
GstStructure *qt_gst_structure_new_empty(const char *name)
{
#if GST_CHECK_VERSION(1,0,0)
return gst_structure_new_empty(name);
#else
return gst_structure_new(name, NULL);
#endif
}
gboolean qt_gst_element_query_position(GstElement *element, GstFormat format, gint64 *cur)
{
#if GST_CHECK_VERSION(1,0,0)
return gst_element_query_position(element, format, cur);
#else
return gst_element_query_position(element, &format, cur);
#endif
}
gboolean qt_gst_element_query_duration(GstElement *element, GstFormat format, gint64 *cur)
{
#if GST_CHECK_VERSION(1,0,0)
return gst_element_query_duration(element, format, cur);
#else
return gst_element_query_duration(element, &format, cur);
#endif
}
QDebug operator <<(QDebug debug, GstCaps *caps)
{
if (caps) {
gchar *string = gst_caps_to_string(caps);
debug = debug << string;
g_free(string);
}
return debug;
}
QT_END_NAMESPACE

View File

@@ -35,21 +35,35 @@
QT_BEGIN_NAMESPACE
#if GST_CHECK_VERSION(1,0,0)
QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info)
: QAbstractPlanarVideoBuffer(NoHandle)
, m_videoInfo(info)
#else
QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine)
: QAbstractVideoBuffer(NoHandle)
, m_buffer(buffer)
, m_bytesPerLine(bytesPerLine)
#endif
, m_buffer(buffer)
, m_mode(NotMapped)
{
gst_buffer_ref(m_buffer);
}
#if GST_CHECK_VERSION(1,0,0)
QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info,
QGstVideoBuffer::HandleType handleType,
const QVariant &handle)
: QAbstractPlanarVideoBuffer(handleType)
, m_videoInfo(info)
#else
QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine,
QGstVideoBuffer::HandleType handleType,
const QVariant &handle)
: QAbstractVideoBuffer(handleType)
, m_buffer(buffer)
, m_bytesPerLine(bytesPerLine)
#endif
, m_buffer(buffer)
, m_mode(NotMapped)
, m_handle(handle)
{
@@ -58,6 +72,8 @@ QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine,
QGstVideoBuffer::~QGstVideoBuffer()
{
unmap();
gst_buffer_unref(m_buffer);
}
@@ -67,12 +83,49 @@ QAbstractVideoBuffer::MapMode QGstVideoBuffer::mapMode() const
return m_mode;
}
#if GST_CHECK_VERSION(1,0,0)
int QGstVideoBuffer::map(MapMode mode, int *numBytes, int bytesPerLine[4], uchar *data[4])
{
const GstMapFlags flags = GstMapFlags(((mode & ReadOnly) ? GST_MAP_READ : 0)
| ((mode & WriteOnly) ? GST_MAP_WRITE : 0));
if (mode == NotMapped || m_mode != NotMapped) {
return 0;
} else if (m_videoInfo.finfo->n_planes == 0) { // Encoded
if (gst_buffer_map(m_buffer, &m_frame.map[0], flags)) {
if (numBytes)
*numBytes = m_frame.map[0].size;
bytesPerLine[0] = -1;
data[0] = static_cast<uchar *>(m_frame.map[0].data);
m_mode = mode;
return 1;
}
} else if (gst_video_frame_map(&m_frame, &m_videoInfo, m_buffer, flags)) {
if (numBytes)
*numBytes = m_frame.info.size;
for (guint i = 0; i < m_frame.info.finfo->n_planes; ++i) {
bytesPerLine[i] = m_frame.info.stride[i];
data[i] = static_cast<uchar *>(m_frame.data[i]);
}
m_mode = mode;
return m_frame.info.finfo->n_planes;
}
return 0;
}
#else
uchar *QGstVideoBuffer::map(MapMode mode, int *numBytes, int *bytesPerLine)
{
if (mode != NotMapped && m_mode == NotMapped) {
if (numBytes)
*numBytes = m_buffer->size;
if (bytesPerLine)
*bytesPerLine = m_bytesPerLine;
@@ -83,8 +136,19 @@ uchar *QGstVideoBuffer::map(MapMode mode, int *numBytes, int *bytesPerLine)
return 0;
}
}
#endif
void QGstVideoBuffer::unmap()
{
#if GST_CHECK_VERSION(1,0,0)
if (m_mode != NotMapped) {
if (m_videoInfo.finfo->n_planes == 0)
gst_buffer_unmap(m_buffer, &m_frame.map[0]);
else
gst_video_frame_unmap(&m_frame);
}
#endif
m_mode = NotMapped;
}

View File

@@ -0,0 +1,53 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qgstvideorendererplugin_p.h"
QT_BEGIN_NAMESPACE
QGstVideoRendererPlugin::QGstVideoRendererPlugin(QObject *parent) :
QObject(parent)
{
}
QT_END_NAMESPACE
#include "moc_qgstvideorendererplugin_p.cpp"

View File

@@ -0,0 +1,605 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qabstractvideosurface.h>
#include <qvideoframe.h>
#include <QDebug>
#include <QMap>
#include <QThread>
#include <QEvent>
#include <QCoreApplication>
#include <private/qmediapluginloader_p.h>
#include "qgstvideobuffer_p.h"
#include "qgstvideorenderersink_p.h"
#include <gst/video/video.h>
#include "qgstutils_p.h"
//#define DEBUG_VIDEO_SURFACE_SINK
QT_BEGIN_NAMESPACE
QGstDefaultVideoRenderer::QGstDefaultVideoRenderer()
: m_flushed(true)
{
}
QGstDefaultVideoRenderer::~QGstDefaultVideoRenderer()
{
}
GstCaps *QGstDefaultVideoRenderer::getCaps(QAbstractVideoSurface *surface)
{
return QGstUtils::capsForFormats(surface->supportedPixelFormats());
}
bool QGstDefaultVideoRenderer::start(QAbstractVideoSurface *surface, GstCaps *caps)
{
m_flushed = true;
m_format = QGstUtils::formatForCaps(caps, &m_videoInfo);
return m_format.isValid() && surface->start(m_format);
}
void QGstDefaultVideoRenderer::stop(QAbstractVideoSurface *surface)
{
m_flushed = true;
if (surface)
surface->stop();
}
bool QGstDefaultVideoRenderer::present(QAbstractVideoSurface *surface, GstBuffer *buffer)
{
m_flushed = false;
QVideoFrame frame(
new QGstVideoBuffer(buffer, m_videoInfo),
m_format.frameSize(),
m_format.pixelFormat());
QGstUtils::setFrameTimeStamps(&frame, buffer);
return surface->present(frame);
}
void QGstDefaultVideoRenderer::flush(QAbstractVideoSurface *surface)
{
if (surface && !m_flushed)
surface->present(QVideoFrame());
m_flushed = true;
}
bool QGstDefaultVideoRenderer::proposeAllocation(GstQuery *)
{
return true;
}
Q_GLOBAL_STATIC_WITH_ARGS(QMediaPluginLoader, rendererLoader,
(QGstVideoRendererInterface_iid, QLatin1String("video/gstvideorenderer"), Qt::CaseInsensitive))
QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface)
: m_surface(surface)
, m_renderer(0)
, m_activeRenderer(0)
, m_surfaceCaps(0)
, m_startCaps(0)
, m_lastBuffer(0)
, m_notified(false)
, m_stop(false)
, m_render(false)
, m_flush(false)
{
foreach (QObject *instance, rendererLoader()->instances(QGstVideoRendererPluginKey)) {
QGstVideoRendererInterface* plugin = qobject_cast<QGstVideoRendererInterface*>(instance);
if (QGstVideoRenderer *renderer = plugin ? plugin->createRenderer() : 0)
m_renderers.append(renderer);
}
m_renderers.append(new QGstDefaultVideoRenderer);
updateSupportedFormats();
connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(updateSupportedFormats()));
}
QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate()
{
qDeleteAll(m_renderers);
if (m_surfaceCaps)
gst_caps_unref(m_surfaceCaps);
}
GstCaps *QVideoSurfaceGstDelegate::caps()
{
QMutexLocker locker(&m_mutex);
gst_caps_ref(m_surfaceCaps);
return m_surfaceCaps;
}
bool QVideoSurfaceGstDelegate::start(GstCaps *caps)
{
QMutexLocker locker(&m_mutex);
if (m_activeRenderer) {
m_flush = true;
m_stop = true;
}
m_render = false;
if (m_lastBuffer) {
gst_buffer_unref(m_lastBuffer);
m_lastBuffer = 0;
}
if (m_startCaps)
gst_caps_unref(m_startCaps);
m_startCaps = caps;
gst_caps_ref(m_startCaps);
/*
Waiting for start() to be invoked in the main thread may block
if gstreamer blocks the main thread until this call is finished.
This situation is rare and usually caused by setState(Null)
while pipeline is being prerolled.
The proper solution to this involves controlling gstreamer pipeline from
other thread than video surface.
Currently start() fails if wait() timed out.
*/
if (!waitForAsyncEvent(&locker, &m_setupCondition, 1000) && m_startCaps) {
qWarning() << "Failed to start video surface due to main thread blocked.";
gst_caps_unref(m_startCaps);
m_startCaps = 0;
}
return m_activeRenderer != 0;
}
void QVideoSurfaceGstDelegate::stop()
{
QMutexLocker locker(&m_mutex);
if (!m_activeRenderer)
return;
m_flush = true;
m_stop = true;
if (m_startCaps) {
gst_caps_unref(m_startCaps);
m_startCaps = 0;
}
if (m_lastBuffer) {
gst_buffer_unref(m_lastBuffer);
m_lastBuffer = 0;
}
waitForAsyncEvent(&locker, &m_setupCondition, 500);
}
bool QVideoSurfaceGstDelegate::proposeAllocation(GstQuery *query)
{
QMutexLocker locker(&m_mutex);
if (QGstVideoRenderer *pool = m_activeRenderer) {
locker.unlock();
return pool->proposeAllocation(query);
} else {
return false;
}
}
void QVideoSurfaceGstDelegate::flush()
{
QMutexLocker locker(&m_mutex);
m_flush = true;
m_render = false;
if (m_lastBuffer) {
gst_buffer_unref(m_lastBuffer);
m_lastBuffer = 0;
}
notify();
}
GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer, bool show)
{
QMutexLocker locker(&m_mutex);
if (m_lastBuffer)
gst_buffer_unref(m_lastBuffer);
m_lastBuffer = buffer;
gst_buffer_ref(m_lastBuffer);
if (show) {
m_render = true;
return waitForAsyncEvent(&locker, &m_renderCondition, 300)
? m_renderReturn
: GST_FLOW_ERROR;
} else {
return GST_FLOW_OK;
}
}
void QVideoSurfaceGstDelegate::handleShowPrerollChange(GObject *object, GParamSpec *, gpointer d)
{
QVideoSurfaceGstDelegate * const delegate = static_cast<QVideoSurfaceGstDelegate *>(d);
gboolean showPreroll = true; // "show-preroll-frame" property is true by default
g_object_get(object, "show-preroll-frame", &showPreroll, NULL);
GstState state = GST_STATE_NULL;
GstState pendingState = GST_STATE_NULL;
gst_element_get_state(GST_ELEMENT(object), &state, &pendingState, 0);
const bool paused
= (pendingState == GST_STATE_VOID_PENDING && state == GST_STATE_PAUSED)
|| pendingState == GST_STATE_PAUSED;
if (paused) {
QMutexLocker locker(&delegate->m_mutex);
if (!showPreroll && delegate->m_lastBuffer) {
delegate->m_render = false;
delegate->m_flush = true;
delegate->notify();
} else if (delegate->m_lastBuffer) {
delegate->m_render = true;
delegate->notify();
}
}
}
bool QVideoSurfaceGstDelegate::event(QEvent *event)
{
if (event->type() == QEvent::UpdateRequest) {
QMutexLocker locker(&m_mutex);
if (m_notified) {
while (handleEvent(&locker)) {}
m_notified = false;
}
return true;
} else {
return QObject::event(event);
}
}
bool QVideoSurfaceGstDelegate::handleEvent(QMutexLocker *locker)
{
if (m_flush) {
m_flush = false;
if (m_activeRenderer) {
locker->unlock();
m_activeRenderer->flush(m_surface);
}
} else if (m_stop) {
m_stop = false;
if (QGstVideoRenderer * const activePool = m_activeRenderer) {
m_activeRenderer = 0;
locker->unlock();
activePool->stop(m_surface);
locker->relock();
}
} else if (m_startCaps) {
Q_ASSERT(!m_activeRenderer);
GstCaps * const startCaps = m_startCaps;
m_startCaps = 0;
if (m_renderer && m_surface) {
locker->unlock();
const bool started = m_renderer->start(m_surface, startCaps);
locker->relock();
m_activeRenderer = started
? m_renderer
: 0;
} else if (QGstVideoRenderer * const activePool = m_activeRenderer) {
m_activeRenderer = 0;
locker->unlock();
activePool->stop(m_surface);
locker->relock();
}
gst_caps_unref(startCaps);
} else if (m_render) {
m_render = false;
if (m_activeRenderer && m_surface && m_lastBuffer) {
GstBuffer *buffer = m_lastBuffer;
gst_buffer_ref(buffer);
locker->unlock();
const bool rendered = m_activeRenderer->present(m_surface, buffer);
gst_buffer_unref(buffer);
locker->relock();
m_renderReturn = rendered
? GST_FLOW_OK
: GST_FLOW_ERROR;
m_renderCondition.wakeAll();
} else {
m_renderReturn = GST_FLOW_ERROR;
m_renderCondition.wakeAll();
}
} else {
m_setupCondition.wakeAll();
return false;
}
return true;
}
void QVideoSurfaceGstDelegate::notify()
{
if (!m_notified) {
m_notified = true;
QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
}
}
bool QVideoSurfaceGstDelegate::waitForAsyncEvent(
QMutexLocker *locker, QWaitCondition *condition, unsigned long time)
{
if (QThread::currentThread() == thread()) {
while (handleEvent(locker)) {}
m_notified = false;
return true;
} else {
notify();
return condition->wait(&m_mutex, time);
}
}
void QVideoSurfaceGstDelegate::updateSupportedFormats()
{
if (m_surfaceCaps) {
gst_caps_unref(m_surfaceCaps);
m_surfaceCaps = 0;
}
foreach (QGstVideoRenderer *pool, m_renderers) {
if (GstCaps *caps = pool->getCaps(m_surface)) {
if (gst_caps_is_empty(caps)) {
gst_caps_unref(caps);
continue;
}
if (m_surfaceCaps)
gst_caps_unref(m_surfaceCaps);
m_renderer = pool;
m_surfaceCaps = caps;
break;
} else {
gst_caps_unref(caps);
}
}
}
static GstVideoSinkClass *sink_parent_class;
#define VO_SINK(s) QGstVideoRendererSink *sink(reinterpret_cast<QGstVideoRendererSink *>(s))
QGstVideoRendererSink *QGstVideoRendererSink::createSink(QAbstractVideoSurface *surface)
{
QGstVideoRendererSink *sink = reinterpret_cast<QGstVideoRendererSink *>(
g_object_new(QGstVideoRendererSink::get_type(), 0));
sink->delegate = new QVideoSurfaceGstDelegate(surface);
g_signal_connect(
G_OBJECT(sink),
"notify::show-preroll-frame",
G_CALLBACK(QVideoSurfaceGstDelegate::handleShowPrerollChange),
sink->delegate);
return sink;
}
GType QGstVideoRendererSink::get_type()
{
static GType type = 0;
if (type == 0) {
static const GTypeInfo info =
{
sizeof(QGstVideoRendererSinkClass), // class_size
base_init, // base_init
NULL, // base_finalize
class_init, // class_init
NULL, // class_finalize
NULL, // class_data
sizeof(QGstVideoRendererSink), // instance_size
0, // n_preallocs
instance_init, // instance_init
0 // value_table
};
type = g_type_register_static(
GST_TYPE_VIDEO_SINK, "QGstVideoRendererSink", &info, GTypeFlags(0));
}
return type;
}
void QGstVideoRendererSink::class_init(gpointer g_class, gpointer class_data)
{
Q_UNUSED(class_data);
sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class));
GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class);
base_sink_class->get_caps = QGstVideoRendererSink::get_caps;
base_sink_class->set_caps = QGstVideoRendererSink::set_caps;
base_sink_class->propose_allocation = QGstVideoRendererSink::propose_allocation;
base_sink_class->preroll = QGstVideoRendererSink::preroll;
base_sink_class->render = QGstVideoRendererSink::render;
GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class);
element_class->change_state = QGstVideoRendererSink::change_state;
GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class);
object_class->finalize = QGstVideoRendererSink::finalize;
}
void QGstVideoRendererSink::base_init(gpointer g_class)
{
static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE(
"sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS(
"video/x-raw, "
"framerate = (fraction) [ 0, MAX ], "
"width = (int) [ 1, MAX ], "
"height = (int) [ 1, MAX ]"));
gst_element_class_add_pad_template(
GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template));
}
void QGstVideoRendererSink::instance_init(GTypeInstance *instance, gpointer g_class)
{
VO_SINK(instance);
Q_UNUSED(g_class);
sink->delegate = 0;
}
void QGstVideoRendererSink::finalize(GObject *object)
{
VO_SINK(object);
delete sink->delegate;
// Chain up
G_OBJECT_CLASS(sink_parent_class)->finalize(object);
}
GstStateChangeReturn QGstVideoRendererSink::change_state(
GstElement *element, GstStateChange transition)
{
Q_UNUSED(element);
return GST_ELEMENT_CLASS(sink_parent_class)->change_state(
element, transition);
}
GstCaps *QGstVideoRendererSink::get_caps(GstBaseSink *base, GstCaps *filter)
{
VO_SINK(base);
GstCaps *caps = sink->delegate->caps();
GstCaps *unfiltered = caps;
if (filter) {
caps = gst_caps_intersect(unfiltered, filter);
gst_caps_unref(unfiltered);
}
return caps;
}
gboolean QGstVideoRendererSink::set_caps(GstBaseSink *base, GstCaps *caps)
{
VO_SINK(base);
#ifdef DEBUG_VIDEO_SURFACE_SINK
qDebug() << "set_caps:";
qDebug() << caps;
#endif
if (!caps) {
sink->delegate->stop();
return TRUE;
} else if (sink->delegate->start(caps)) {
return TRUE;
} else {
return FALSE;
}
}
gboolean QGstVideoRendererSink::propose_allocation(GstBaseSink *base, GstQuery *query)
{
VO_SINK(base);
return sink->delegate->proposeAllocation(query);
}
GstFlowReturn QGstVideoRendererSink::preroll(GstBaseSink *base, GstBuffer *buffer)
{
VO_SINK(base);
gboolean showPreroll = true; // "show-preroll-frame" property is true by default
g_object_get(G_OBJECT(base), "show-preroll-frame", &showPreroll, NULL);
return sink->delegate->render(buffer, showPreroll); // display frame
}
GstFlowReturn QGstVideoRendererSink::render(GstBaseSink *base, GstBuffer *buffer)
{
VO_SINK(base);
return sink->delegate->render(buffer, true);
}
QT_END_NAMESPACE

View File

@@ -41,8 +41,13 @@
#include <private/qmediapluginloader_p.h>
#include "qgstvideobuffer_p.h"
#include "qgstutils_p.h"
#include "qvideosurfacegstsink_p.h"
#if GST_VERSION_MAJOR >=1
#include <gst/video/video.h>
#endif
//#define DEBUG_VIDEO_SURFACE_SINK
QT_BEGIN_NAMESPACE
@@ -62,10 +67,12 @@ QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate(
if (m_surface) {
foreach (QObject *instance, bufferPoolLoader()->instances(QGstBufferPoolPluginKey)) {
QGstBufferPoolInterface* plugin = qobject_cast<QGstBufferPoolInterface*>(instance);
if (plugin) {
m_pools.append(plugin);
}
}
updateSupportedFormats();
connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(updateSupportedFormats()));
}
@@ -191,13 +198,15 @@ GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer)
m_format.frameSize(),
m_format.pixelFormat());
QVideoSurfaceGstSink::setFrameTimeStamps(&m_frame, buffer);
QGstUtils::setFrameTimeStamps(&m_frame, buffer);
m_renderReturn = GST_FLOW_OK;
if (QThread::currentThread() == thread()) {
if (!m_surface.isNull())
m_surface->present(m_frame);
else
qWarning() << "m_surface.isNull().";
} else {
QMetaObject::invokeMethod(this, "queuedRender", Qt::QueuedConnection);
m_renderCondition.wait(&m_mutex, 300);
@@ -283,90 +292,6 @@ void QVideoSurfaceGstDelegate::updateSupportedFormats()
}
}
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;
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;
}
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, int(0xFF000000), 0x00000000 },
{ QVideoFrame::Format_RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 },
{ QVideoFrame::Format_BGR32 , 32, 24, 4321, int(0xFF000000), 0x00FF0000, 0x0000FF00, 0x00000000 },
{ QVideoFrame::Format_BGR32 , 32, 24, 1234, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 },
{ QVideoFrame::Format_ARGB32, 32, 24, 4321, 0x0000FF00, 0x00FF0000, int(0xFF000000), 0x000000FF },
{ QVideoFrame::Format_ARGB32, 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, int(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;
}
static GstVideoSinkClass *sink_parent_class;
#define VO_SINK(s) QVideoSurfaceGstSink *sink(reinterpret_cast<QVideoSurfaceGstSink *>(s))
@@ -494,8 +419,6 @@ GstCaps *QVideoSurfaceGstSink::get_caps(GstBaseSink *base)
{
VO_SINK(base);
GstCaps *caps = gst_caps_new_empty();
// Find the supported pixel formats
// with buffer pool specific formats listed first
QList<QVideoFrame::PixelFormat> supportedFormats;
@@ -503,6 +426,7 @@ GstCaps *QVideoSurfaceGstSink::get_caps(GstBaseSink *base)
QList<QVideoFrame::PixelFormat> poolHandleFormats;
sink->delegate->poolMutex()->lock();
QGstBufferPoolInterface *pool = sink->delegate->pool();
if (pool)
poolHandleFormats = sink->delegate->supportedPixelFormats(pool->handleType());
sink->delegate->poolMutex()->unlock();
@@ -513,47 +437,7 @@ GstCaps *QVideoSurfaceGstSink::get_caps(GstBaseSink *base)
supportedFormats.append(format);
}
foreach (QVideoFrame::PixelFormat format, supportedFormats) {
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;
return QGstUtils::capsForFormats(supportedFormats);
}
gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps)
@@ -575,7 +459,7 @@ gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps)
QAbstractVideoBuffer::HandleType handleType =
pool ? pool->handleType() : QAbstractVideoBuffer::NoHandle;
QVideoSurfaceFormat format = formatForCaps(caps, &bytesPerLine, handleType);
QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &bytesPerLine, handleType);
if (sink->delegate->isActive()) {
QVideoSurfaceFormat surfaceFormst = sink->delegate->surfaceFormat();
@@ -592,7 +476,7 @@ gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps)
sink->lastRequestedCaps = 0;
#ifdef DEBUG_VIDEO_SURFACE_SINK
qDebug() << "Staring video surface, format:";
qDebug() << "Starting video surface, format:";
qDebug() << format;
qDebug() << "bytesPerLine:" << bytesPerLine;
#endif
@@ -606,87 +490,6 @@ gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps)
return FALSE;
}
QVideoSurfaceFormat QVideoSurfaceGstSink::formatForCaps(GstCaps *caps, int *bytesPerLine, QAbstractVideoBuffer::HandleType handleType)
{
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, handleType);
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();
}
void QVideoSurfaceGstSink::setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer)
{
// GStreamer uses nanoseconds, Qt uses microseconds
qint64 startTime = GST_BUFFER_TIMESTAMP(buffer);
if (startTime >= 0) {
frame->setStartTime(startTime/G_GINT64_CONSTANT (1000));
qint64 duration = GST_BUFFER_DURATION(buffer);
if (duration >= 0)
frame->setEndTime((startTime + duration)/G_GINT64_CONSTANT (1000));
}
}
GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer)
{
@@ -731,7 +534,7 @@ GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
if (sink->delegate->isActive()) {
//if format was changed, restart the surface
QVideoSurfaceFormat format = formatForCaps(intersection);
QVideoSurfaceFormat format = QGstUtils::formatForCaps(intersection);
QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat();
if (format.pixelFormat() != surfaceFormat.pixelFormat() ||
@@ -749,7 +552,7 @@ GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
QAbstractVideoBuffer::HandleType handleType =
pool ? pool->handleType() : QAbstractVideoBuffer::NoHandle;
QVideoSurfaceFormat format = formatForCaps(intersection, &bytesPerLine, handleType);
QVideoSurfaceFormat format = QGstUtils::formatForCaps(intersection, &bytesPerLine, handleType);
if (!sink->delegate->start(format, bytesPerLine)) {
qWarning() << "failed to start video surface";
@@ -763,7 +566,7 @@ GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat();
if (!pool->isFormatSupported(surfaceFormat)) {
//qDebug() << "sink doesn't support native pool format, skip custom buffers allocation";
qDebug() << "sink doesn't support native pool format, skip custom buffers allocation";
return GST_FLOW_OK;
}
@@ -787,7 +590,6 @@ GstFlowReturn QVideoSurfaceGstSink::buffer_alloc(
gboolean QVideoSurfaceGstSink::start(GstBaseSink *base)
{
Q_UNUSED(base);
return TRUE;
}

View File

@@ -39,7 +39,10 @@
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
#if GST_VERSION_MAJOR < 1
#include <gst/app/gstappbuffer.h>
#endif
QT_BEGIN_NAMESPACE

View File

@@ -38,23 +38,32 @@
#include <qmediaaudioprobecontrol.h>
#include <QtCore/qmutex.h>
#include <qaudiobuffer.h>
#include <qshareddata.h>
#include <private/qgstreamerbufferprobe_p.h>
QT_BEGIN_NAMESPACE
class QGstreamerAudioProbeControl : public QMediaAudioProbeControl
class QGstreamerAudioProbeControl
: public QMediaAudioProbeControl
, public QGstreamerBufferProbe
, public QSharedData
{
Q_OBJECT
public:
explicit QGstreamerAudioProbeControl(QObject *parent);
virtual ~QGstreamerAudioProbeControl();
void bufferProbed(GstBuffer* buffer);
protected:
void probeCaps(GstCaps *caps);
bool probeBuffer(GstBuffer *buffer);
private slots:
void bufferProbed();
private:
QAudioBuffer m_pendingBuffer;
QAudioFormat m_format;
QMutex m_bufferMutex;
};

View File

@@ -0,0 +1,86 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QGSTREAMERBUFFERPROBE_H
#define QGSTREAMERBUFFERPROBE_H
#include <gst/gst.h>
#include <QtCore/qglobal.h>
QT_BEGIN_NAMESPACE
class QGstreamerBufferProbe
{
public:
enum Flags
{
ProbeCaps = 0x01,
ProbeBuffers = 0x02,
ProbeAll = ProbeCaps | ProbeBuffers
};
explicit QGstreamerBufferProbe(Flags flags = ProbeAll);
virtual ~QGstreamerBufferProbe();
void addProbeToPad(GstPad *pad, bool downstream = true);
void removeProbeFromPad(GstPad *pad);
protected:
virtual void probeCaps(GstCaps *caps);
virtual bool probeBuffer(GstBuffer *buffer);
private:
#if GST_CHECK_VERSION(1,0,0)
static GstPadProbeReturn capsProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data);
static GstPadProbeReturn bufferProbe(GstPad *pad, GstPadProbeInfo *info, gpointer user_data);
int m_capsProbeId;
#else
static gboolean bufferProbe(GstElement *element, GstBuffer *buffer, gpointer user_data);
GstCaps *m_caps;
#endif
int m_bufferProbeId;
const Flags m_flags;
};
QT_END_NAMESPACE
#endif // QGSTREAMERAUDIOPROBECONTROL_H

View File

@@ -0,0 +1,102 @@
/****************************************************************************
**
** Copyright (C) 2014 Canonical Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QGSTREAMERMIRTEXTURERENDERER_H
#define QGSTREAMERMIRTEXTURERENDERER_H
#include <qmediaplayer.h>
#include <qvideorenderercontrol.h>
#include <private/qvideosurfacegstsink_p.h>
#include <qabstractvideosurface.h>
#include "qgstreamervideorendererinterface_p.h"
QT_BEGIN_NAMESPACE
class QGstreamerMirTextureBuffer;
class QGstreamerPlayerSession;
class QGLContext;
class QOpenGLContext;
class QSurfaceFormat;
class QGstreamerMirTextureRenderer : public QVideoRendererControl, public QGstreamerVideoRendererInterface
{
Q_OBJECT
Q_INTERFACES(QGstreamerVideoRendererInterface)
public:
QGstreamerMirTextureRenderer(QObject *parent = 0, const QGstreamerPlayerSession *playerSession = 0);
virtual ~QGstreamerMirTextureRenderer();
QAbstractVideoSurface *surface() const;
void setSurface(QAbstractVideoSurface *surface);
void setPlayerSession(const QGstreamerPlayerSession *playerSession);
GstElement *videoSink();
void stopRenderer();
bool isReady() const { return m_surface != 0; }
signals:
void sinkChanged();
void readyChanged(bool);
void nativeSizeChanged();
private slots:
void handleFormatChange();
void updateNativeVideoSize();
void handleFocusWindowChanged(QWindow *window);
void renderFrame();
private:
QWindow *createOffscreenWindow(const QSurfaceFormat &format);
static void handleFrameReady(gpointer userData);
static GstPadProbeReturn padBufferProbe(GstPad *pad, GstPadProbeInfo *info, gpointer userData);
GstElement *m_videoSink;
QPointer<QAbstractVideoSurface> m_surface;
QPointer<QAbstractVideoSurface> m_glSurface;
QGLContext *m_context;
QOpenGLContext *m_glContext;
unsigned int m_textureId;
QWindow *m_offscreenSurface;
QGstreamerPlayerSession *m_playerSession;
QGstreamerMirTextureBuffer *m_textureBuffer;
QSize m_nativeSize;
QMutex m_mutex;
};
QT_END_NAMESPACE
#endif // QGSTREAMERMIRTEXTURERENDRER_H

View File

@@ -35,20 +35,29 @@
#define QGSTREAMERVIDEOPROBECONTROL_H
#include <gst/gst.h>
#include <gst/video/video.h>
#include <qmediavideoprobecontrol.h>
#include <QtCore/qmutex.h>
#include <qvideoframe.h>
#include <qvideosurfaceformat.h>
#include <private/qgstreamerbufferprobe_p.h>
QT_BEGIN_NAMESPACE
class QGstreamerVideoProbeControl : public QMediaVideoProbeControl
class QGstreamerVideoProbeControl
: public QMediaVideoProbeControl
, public QGstreamerBufferProbe
, public QSharedData
{
Q_OBJECT
public:
explicit QGstreamerVideoProbeControl(QObject *parent);
virtual ~QGstreamerVideoProbeControl();
void bufferProbed(GstBuffer* buffer);
void probeCaps(GstCaps *caps);
bool probeBuffer(GstBuffer *buffer);
void startFlushing();
void stopFlushing();
@@ -56,10 +65,16 @@ private slots:
void frameProbed();
private:
bool m_flushing;
bool m_frameProbed; // true if at least one frame was probed
QVideoSurfaceFormat m_format;
QVideoFrame m_pendingFrame;
QMutex m_frameMutex;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo m_videoInfo;
#else
int m_bytesPerLine;
#endif
bool m_flushing;
bool m_frameProbed; // true if at least one frame was probed
};
QT_END_NAMESPACE

View File

@@ -38,6 +38,7 @@
#include "qgstreamervideorendererinterface_p.h"
#include <private/qgstreamerbushelper_p.h>
#include <private/qgstreamerbufferprobe_p.h>
#include <QtGui/qcolor.h>
QT_BEGIN_NAMESPACE
@@ -45,7 +46,8 @@ class QAbstractVideoSurface;
class QGstreamerVideoWindow : public QVideoWindowControl,
public QGstreamerVideoRendererInterface,
public QGstreamerSyncMessageFilter
public QGstreamerSyncMessageFilter,
private QGstreamerBufferProbe
{
Q_OBJECT
Q_INTERFACES(QGstreamerVideoRendererInterface QGstreamerSyncMessageFilter)
@@ -101,10 +103,10 @@ signals:
void readyChanged(bool);
private slots:
void updateNativeVideoSize();
void updateNativeVideoSize(const QSize &size);
private:
static void padBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data);
void probeCaps(GstCaps *caps);
GstElement *m_videoSink;
WId m_windowId;
@@ -113,7 +115,6 @@ private:
bool m_fullScreen;
QSize m_nativeSize;
mutable QColor m_colorKey;
int m_bufferProbeId;
};
QT_END_NAMESPACE

View File

@@ -49,14 +49,32 @@
#include <QtCore/qset.h>
#include <QtCore/qvector.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <qaudioformat.h>
#include <qcamera.h>
#include <qabstractvideobuffer.h>
#include <qvideoframe.h>
#include <QDebug>
#if GST_CHECK_VERSION(1,0,0)
# define QT_GSTREAMER_PLAYBIN_ELEMENT_NAME "playbin"
# define QT_GSTREAMER_CAMERABIN_ELEMENT_NAME "camerabin"
# define QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME "videoconvert"
# define QT_GSTREAMER_RAW_AUDIO_MIME "audio/x-raw"
#else
# define QT_GSTREAMER_PLAYBIN_ELEMENT_NAME "playbin2"
# define QT_GSTREAMER_CAMERABIN_ELEMENT_NAME "camerabin2"
# define QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME "ffmpegcolorspace"
# define QT_GSTREAMER_RAW_AUDIO_MIME "audio/x-raw-int"
#endif
QT_BEGIN_NAMESPACE
class QSize;
class QVariant;
class QByteArray;
class QImage;
class QVideoSurfaceFormat;
namespace QGstUtils {
struct CameraInfo
@@ -73,8 +91,12 @@ namespace QGstUtils {
QSize capsResolution(const GstCaps *caps);
QSize capsCorrectedResolution(const GstCaps *caps);
QAudioFormat audioFormatForCaps(const GstCaps *caps);
#if GST_CHECK_VERSION(1,0,0)
QAudioFormat audioFormatForSample(GstSample *sample);
#else
QAudioFormat audioFormatForBuffer(GstBuffer *buffer);
GstCaps *capsForAudioFormat(QAudioFormat format);
#endif
GstCaps *capsForAudioFormat(const QAudioFormat &format);
void initializeGst();
QMultimedia::SupportEstimate hasSupport(const QString &mimeType,
const QStringList &codecs,
@@ -86,9 +108,40 @@ namespace QGstUtils {
QCamera::Position cameraPosition(const QString &device, GstElementFactory * factory = 0);
int cameraOrientation(const QString &device, GstElementFactory * factory = 0);
QByteArray cameraDriver(const QString &device, GstElementFactory * factory = 0);
QSet<QString> supportedMimeTypes(bool (*isValidFactory)(GstElementFactory *factory));
#if GST_CHECK_VERSION(1,0,0)
QImage bufferToImage(GstBuffer *buffer, const GstVideoInfo &info);
QVideoSurfaceFormat formatForCaps(
GstCaps *caps,
GstVideoInfo *info,
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle);
#else
QImage bufferToImage(GstBuffer *buffer);
QVideoSurfaceFormat formatForCaps(
GstCaps *caps,
int *bytesPerLine = 0,
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle);
#endif
GstCaps *capsForFormats(const QList<QVideoFrame::PixelFormat> &formats);
void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer);
void setMetaData(GstElement *element, const QMap<QByteArray, QVariant> &data);
void setMetaData(GstBin *bin, const QMap<QByteArray, QVariant> &data);
GstCaps *videoFilterCaps();
}
void qt_gst_object_ref_sink(gpointer object);
GstCaps *qt_gst_pad_get_current_caps(GstPad *pad);
GstStructure *qt_gst_structure_new_empty(const char *name);
gboolean qt_gst_element_query_position(GstElement *element, GstFormat format, gint64 *cur);
gboolean qt_gst_element_query_duration(GstElement *element, GstFormat format, gint64 *cur);
QDebug operator <<(QDebug debug, GstCaps *caps);
QT_END_NAMESPACE

View File

@@ -49,26 +49,47 @@
#include <QtCore/qvariant.h>
#include <gst/gst.h>
#include <gst/video/video.h>
QT_BEGIN_NAMESPACE
#if GST_CHECK_VERSION(1,0,0)
class QGstVideoBuffer : public QAbstractPlanarVideoBuffer
{
public:
QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info);
QGstVideoBuffer(GstBuffer *buffer, const GstVideoInfo &info,
HandleType handleType, const QVariant &handle);
#else
class QGstVideoBuffer : public QAbstractVideoBuffer
{
public:
QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine);
QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine,
HandleType handleType, const QVariant &handle);
#endif
~QGstVideoBuffer();
MapMode mapMode() const;
#if GST_CHECK_VERSION(1,0,0)
int map(MapMode mode, int *numBytes, int bytesPerLine[4], uchar *data[4]);
#else
uchar *map(MapMode mode, int *numBytes, int *bytesPerLine);
#endif
void unmap();
QVariant handle() const { return m_handle; }
private:
GstBuffer *m_buffer;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo m_videoInfo;
GstVideoFrame m_frame;
#else
int m_bytesPerLine;
#endif
GstBuffer *m_buffer;
MapMode m_mode;
QVariant m_handle;
};

View File

@@ -0,0 +1,111 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QGSTVIDEORENDERERPLUGIN_P_H
#define QGSTVIDEORENDERERPLUGIN_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 <qabstractvideobuffer.h>
#include <qvideosurfaceformat.h>
#include <QtCore/qobject.h>
#include <QtCore/qplugin.h>
#include <gst/gst.h>
QT_BEGIN_NAMESPACE
class QAbstractVideoSurface;
const QLatin1String QGstVideoRendererPluginKey("gstvideorenderer");
class QGstVideoRenderer
{
public:
virtual ~QGstVideoRenderer() {}
virtual GstCaps *getCaps(QAbstractVideoSurface *surface) = 0;
virtual bool start(QAbstractVideoSurface *surface, GstCaps *caps) = 0;
virtual void stop(QAbstractVideoSurface *surface) = 0; // surface may be null if unexpectedly deleted.
virtual bool proposeAllocation(GstQuery *query) = 0; // may be called from a thread.
virtual bool present(QAbstractVideoSurface *surface, GstBuffer *buffer) = 0;
virtual void flush(QAbstractVideoSurface *surface) = 0; // surface may be null if unexpectedly deleted.
};
/*
Abstract interface for video buffers allocation.
*/
class QGstVideoRendererInterface
{
public:
virtual ~QGstVideoRendererInterface() {}
virtual QGstVideoRenderer *createRenderer() = 0;
};
#define QGstVideoRendererInterface_iid "org.qt-project.qt.gstvideorenderer/5.4"
Q_DECLARE_INTERFACE(QGstVideoRendererInterface, QGstVideoRendererInterface_iid)
class QGstVideoRendererPlugin : public QObject, public QGstVideoRendererInterface
{
Q_OBJECT
Q_INTERFACES(QGstVideoRendererInterface)
public:
explicit QGstVideoRendererPlugin(QObject *parent = 0);
virtual ~QGstVideoRendererPlugin() {}
virtual QGstVideoRenderer *createRenderer() = 0;
};
QT_END_NAMESPACE
#endif

View File

@@ -0,0 +1,183 @@
/****************************************************************************
**
** Copyright (C) 2014 Jolla Ltd.
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QGSTVIDEORENDERERSINK_P_H
#define QGSTVIDEORENDERERSINK_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 <gst/video/gstvideosink.h>
#include <gst/video/video.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>
#include "qgstvideorendererplugin_p.h"
#include "qgstvideorendererplugin_p.h"
QT_BEGIN_NAMESPACE
class QAbstractVideoSurface;
class QGstDefaultVideoRenderer : public QGstVideoRenderer
{
public:
QGstDefaultVideoRenderer();
~QGstDefaultVideoRenderer();
GstCaps *getCaps(QAbstractVideoSurface *surface);
bool start(QAbstractVideoSurface *surface, GstCaps *caps);
void stop(QAbstractVideoSurface *surface);
bool proposeAllocation(GstQuery *query);
bool present(QAbstractVideoSurface *surface, GstBuffer *buffer);
void flush(QAbstractVideoSurface *surface);
private:
QVideoSurfaceFormat m_format;
GstVideoInfo m_videoInfo;
bool m_flushed;
};
class QVideoSurfaceGstDelegate : public QObject
{
Q_OBJECT
public:
QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface);
~QVideoSurfaceGstDelegate();
GstCaps *caps();
bool start(GstCaps *caps);
void stop();
bool proposeAllocation(GstQuery *query);
void flush();
GstFlowReturn render(GstBuffer *buffer, bool show);
bool event(QEvent *event);
static void handleShowPrerollChange(GObject *o, GParamSpec *p, gpointer d);
private slots:
bool handleEvent(QMutexLocker *locker);
void updateSupportedFormats();
private:
void notify();
bool waitForAsyncEvent(QMutexLocker *locker, QWaitCondition *condition, unsigned long time);
QPointer<QAbstractVideoSurface> m_surface;
QMutex m_mutex;
QWaitCondition m_setupCondition;
QWaitCondition m_renderCondition;
GstFlowReturn m_renderReturn;
QList<QGstVideoRenderer *> m_renderers;
QGstVideoRenderer *m_renderer;
QGstVideoRenderer *m_activeRenderer;
GstCaps *m_surfaceCaps;
GstCaps *m_startCaps;
GstBuffer *m_lastBuffer;
bool m_notified;
bool m_stop;
bool m_render;
bool m_flush;
};
class QGstVideoRendererSink
{
public:
GstVideoSink parent;
static QGstVideoRendererSink *createSink(QAbstractVideoSurface *surface);
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 finalize(GObject *object);
static GstStateChangeReturn change_state(GstElement *element, GstStateChange transition);
static GstCaps *get_caps(GstBaseSink *sink, GstCaps *filter);
static gboolean set_caps(GstBaseSink *sink, GstCaps *caps);
static gboolean propose_allocation(GstBaseSink *sink, GstQuery *query);
static GstFlowReturn preroll(GstBaseSink *sink, GstBuffer *buffer);
static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer);
private:
QVideoSurfaceGstDelegate *delegate;
};
class QGstVideoRendererSinkClass
{
public:
GstVideoSinkClass parent_class;
};
QT_END_NAMESPACE
#endif

View File

@@ -45,6 +45,18 @@
// We mean it.
//
#include <gst/gst.h>
#if GST_CHECK_VERSION(1,0,0)
#include "qgstvideorenderersink_p.h"
QT_BEGIN_NAMESPACE
typedef QGstVideoRendererSink QVideoSurfaceGstSink;
QT_END_NAMESPACE
#else
#include <gst/video/gstvideosink.h>
#include <QtCore/qlist.h>
@@ -116,10 +128,6 @@ public:
GstVideoSink parent;
static QVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface);
static QVideoSurfaceFormat formatForCaps(GstCaps *caps,
int *bytesPerLine = 0,
QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle);
static void setFrameTimeStamps(QVideoFrame *frame, GstBuffer *buffer);
private:
static GType get_type();
@@ -150,7 +158,6 @@ private:
QVideoSurfaceFormat *lastSurfaceFormat;
};
class QVideoSurfaceGstSinkClass
{
public:
@@ -160,3 +167,5 @@ public:
QT_END_NAMESPACE
#endif
#endif

View File

@@ -4,6 +4,8 @@ QT = core-private network gui-private
MODULE_PLUGIN_TYPES = \
mediaservice \
audio \
video/bufferpool \
video/gstvideorenderer \
video/videonode \
playlistformats

View File

@@ -68,89 +68,16 @@ QMultimedia::SupportEstimate QGstreamerAudioDecoderServicePlugin::hasSupport(con
return QGstUtils::hasSupport(mimeType, codecs, m_supportedMimeTypeSet);
}
static bool isDecoderOrDemuxer(GstElementFactory *factory)
{
return gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DEMUXER)
|| gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DECODER
| GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO);
}
void QGstreamerAudioDecoderServicePlugin::updateSupportedMimeTypes() const
{
//enumerate supported mime types
gst_init(NULL, NULL);
GList *plugins, *orig_plugins;
orig_plugins = plugins = gst_default_registry_get_plugin_list ();
while (plugins) {
GList *features, *orig_features;
GstPlugin *plugin = (GstPlugin *) (plugins->data);
plugins = g_list_next (plugins);
if (plugin->flags & (1<<1)) //GST_PLUGIN_FLAG_BLACKLISTED
continue;
orig_features = features = gst_registry_get_feature_list_by_plugin(gst_registry_get_default (),
plugin->desc.name);
while (features) {
if (!G_UNLIKELY(features->data == NULL)) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE(features->data);
if (GST_IS_ELEMENT_FACTORY (feature)) {
GstElementFactory *factory = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature));
if (factory
&& factory->numpadtemplates > 0
&& (qstrcmp(factory->details.klass, "Codec/Decoder/Audio") == 0
|| qstrcmp(factory->details.klass, "Codec/Demux") == 0 )) {
const GList *pads = factory->staticpadtemplates;
while (pads) {
GstStaticPadTemplate *padtemplate = (GstStaticPadTemplate*)(pads->data);
pads = g_list_next (pads);
if (padtemplate->direction != GST_PAD_SINK)
continue;
if (padtemplate->static_caps.string) {
GstCaps *caps = gst_static_caps_get(&padtemplate->static_caps);
if (!gst_caps_is_any (caps) && ! gst_caps_is_empty (caps)) {
for (guint i = 0; i < gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
QString nameLowcase = QString(gst_structure_get_name (structure)).toLower();
m_supportedMimeTypeSet.insert(nameLowcase);
if (nameLowcase.contains("mpeg")) {
//Because mpeg version number is only included in the detail
//description, it is necessary to manually extract this information
//in order to match the mime type of mpeg4.
const GValue *value = gst_structure_get_value(structure, "mpegversion");
if (value) {
gchar *str = gst_value_serialize (value);
QString versions(str);
QStringList elements = versions.split(QRegExp("\\D+"), QString::SkipEmptyParts);
foreach (const QString &e, elements)
m_supportedMimeTypeSet.insert(nameLowcase + e);
g_free (str);
}
}
}
}
gst_caps_unref(caps);
}
}
gst_object_unref (factory);
}
} else if (GST_IS_TYPE_FIND_FACTORY(feature)) {
QString name(gst_plugin_feature_get_name(feature));
if (name.contains('/')) //filter out any string without '/' which is obviously not a mime type
m_supportedMimeTypeSet.insert(name.toLower());
}
}
features = g_list_next (features);
}
gst_plugin_feature_list_free (orig_features);
}
gst_plugin_list_free (orig_plugins);
#if defined QT_SUPPORTEDMIMETYPES_DEBUG
QStringList list = m_supportedMimeTypeSet.toList();
list.sort();
if (qgetenv("QT_DEBUG_PLUGINS").toInt() > 0) {
foreach (const QString &type, list)
qDebug() << type;
}
#endif
m_supportedMimeTypeSet = QGstUtils::supportedMimeTypes(isDecoderOrDemuxer);
}
QStringList QGstreamerAudioDecoderServicePlugin::supportedMimeTypes() const

View File

@@ -85,7 +85,7 @@ QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent)
m_durationQueries(0)
{
// Create pipeline here
m_playbin = gst_element_factory_make("playbin2", NULL);
m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, NULL);
if (m_playbin != 0) {
// Sort out messages
@@ -446,21 +446,40 @@ QAudioBuffer QGstreamerAudioDecoderSession::read()
if (buffersAvailable == 1)
emit bufferAvailableChanged(false);
GstBuffer *buffer = gst_app_sink_pull_buffer(m_appSink);
const char* bufferData = 0;
int bufferSize = 0;
#if GST_CHECK_VERSION(1,0,0)
GstSample *sample = gst_app_sink_pull_sample(m_appSink);
GstBuffer *buffer = gst_sample_get_buffer(sample);
GstMapInfo mapInfo;
gst_buffer_map(buffer, &mapInfo, GST_MAP_READ);
bufferData = (const char*)mapInfo.data;
bufferSize = mapInfo.size;
QAudioFormat format = QGstUtils::audioFormatForSample(sample);
#else
GstBuffer *buffer = gst_app_sink_pull_buffer(m_appSink);
bufferData = (const char*)buffer->data;
bufferSize = buffer->size;
QAudioFormat format = QGstUtils::audioFormatForBuffer(buffer);
#endif
if (format.isValid()) {
// XXX At the moment we have to copy data from GstBuffer into QAudioBuffer.
// We could improve performance by implementing QAbstractAudioBuffer for GstBuffer.
qint64 position = getPositionFromBuffer(buffer);
audioBuffer = QAudioBuffer(QByteArray((const char*)buffer->data, buffer->size), format, position);
audioBuffer = QAudioBuffer(QByteArray((const char*)bufferData, bufferSize), format, position);
position /= 1000; // convert to milliseconds
if (position != m_position) {
m_position = position;
emit positionChanged(m_position);
}
}
#if GST_CHECK_VERSION(1,0,0)
gst_sample_unref(sample);
#else
gst_buffer_unref(buffer);
#endif
}
return audioBuffer;
@@ -488,7 +507,7 @@ void QGstreamerAudioDecoderSession::processInvalidMedia(QAudioDecoder::Error err
emit error(int(errorCode), errorString);
}
GstFlowReturn QGstreamerAudioDecoderSession::new_buffer(GstAppSink *, gpointer user_data)
GstFlowReturn QGstreamerAudioDecoderSession::new_sample(GstAppSink *, gpointer user_data)
{
// "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()."
QGstreamerAudioDecoderSession *session = reinterpret_cast<QGstreamerAudioDecoderSession*>(user_data);
@@ -531,7 +550,11 @@ void QGstreamerAudioDecoderSession::addAppSink()
GstAppSinkCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.new_buffer = &new_buffer;
#if GST_CHECK_VERSION(1,0,0)
callbacks.new_sample = &new_sample;
#else
callbacks.new_buffer = &new_sample;
#endif
gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL);
gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE);
gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE);
@@ -553,11 +576,10 @@ void QGstreamerAudioDecoderSession::removeAppSink()
void QGstreamerAudioDecoderSession::updateDuration()
{
GstFormat format = GST_FORMAT_TIME;
gint64 gstDuration = 0;
int duration = -1;
if (m_playbin && gst_element_query_duration(m_playbin, &format, &gstDuration))
if (m_playbin && qt_gst_element_query_duration(m_playbin, GST_FORMAT_TIME, &gstDuration))
duration = gstDuration / 1000000;
if (m_duration != duration) {

View File

@@ -92,7 +92,7 @@ public:
qint64 position() const;
qint64 duration() const;
static GstFlowReturn new_buffer(GstAppSink *sink, gpointer user_data);
static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data);
signals:
void stateChanged(QAudioDecoder::State newState);

View File

@@ -79,7 +79,7 @@ config_gstreamer_photography {
$$PWD/camerabinlocks.cpp \
$$PWD/camerabinzoom.cpp
LIBS += -lgstphotography-0.10
LIBS += -lgstphotography-$$GST_VERSION
DEFINES += GST_USE_UNSTABLE_API #prevents warnings because of unstable photography API
}

View File

@@ -96,7 +96,7 @@ GstEncodingContainerProfile *CameraBinContainer::createProfile()
GstCaps *caps;
if (m_actualFormat.isEmpty()) {
caps = gst_caps_new_any();
return 0;
} else {
QString format = m_actualFormat;
QStringList supportedFormats = m_supportedContainers.supportedCodecs();

View File

@@ -95,11 +95,6 @@ void CameraBinControl::setCaptureMode(QCamera::CaptureModes mode)
captureMode() == QCamera::CaptureStillImage ?
CamerabinResourcePolicy::ImageCaptureResources :
CamerabinResourcePolicy::VideoCaptureResources);
#if (GST_VERSION_MAJOR == 0) && ((GST_VERSION_MINOR < 10) || (GST_VERSION_MICRO < 23))
//due to bug in v4l2src, it's necessary to reload camera on video caps changes
//https://bugzilla.gnome.org/show_bug.cgi?id=649832
reloadLater();
#endif
}
emit captureModeChanged(mode);
}
@@ -299,6 +294,8 @@ bool CameraBinControl::canChangeProperty(PropertyChangeType changeType, QCamera:
switch (changeType) {
case QCameraControl::CaptureMode:
return status != QCamera::ActiveStatus;
break;
case QCameraControl::ImageEncodingSettings:
case QCameraControl::VideoEncodingSettings:
case QCameraControl::Viewfinder:

View File

@@ -37,6 +37,10 @@
#include <QDebug>
#if !GST_CHECK_VERSION(1,0,0)
typedef GstSceneMode GstPhotographySceneMode;
#endif
QT_BEGIN_NAMESPACE
CameraBinExposure::CameraBinExposure(CameraBinSession *session)
@@ -119,7 +123,7 @@ QVariant CameraBinExposure::actualValue(ExposureParameter parameter) const
}
case QCameraExposureControl::ExposureMode:
{
GstSceneMode sceneMode;
GstPhotographySceneMode sceneMode;
gst_photography_get_scene_mode(m_session->photography(), &sceneMode);
switch (sceneMode) {
@@ -167,7 +171,7 @@ bool CameraBinExposure::setValue(ExposureParameter parameter, const QVariant& va
case QCameraExposureControl::ExposureMode:
{
QCameraExposure::ExposureMode mode = QCameraExposure::ExposureMode(value.toInt());
GstSceneMode sceneMode;
GstPhotographySceneMode sceneMode;
gst_photography_get_scene_mode(m_session->photography(), &sceneMode);
switch (mode) {

View File

@@ -37,6 +37,10 @@
#include <QDebug>
#if !GST_CHECK_VERSION(1,0,0)
typedef GstFlashMode GstPhotographyFlashMode;
#endif
QT_BEGIN_NAMESPACE
CameraBinFlash::CameraBinFlash(CameraBinSession *session)
@@ -51,7 +55,7 @@ CameraBinFlash::~CameraBinFlash()
QCameraExposure::FlashModes CameraBinFlash::flashMode() const
{
GstFlashMode flashMode;
GstPhotographyFlashMode flashMode;
gst_photography_get_flash_mode(m_session->photography(), &flashMode);
QCameraExposure::FlashModes modes;
@@ -70,7 +74,7 @@ QCameraExposure::FlashModes CameraBinFlash::flashMode() const
void CameraBinFlash::setFlashMode(QCameraExposure::FlashModes mode)
{
GstFlashMode flashMode;
GstPhotographyFlashMode flashMode;
gst_photography_get_flash_mode(m_session->photography(), &flashMode);
if (mode.testFlag(QCameraExposure::FlashAuto)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_AUTO;

View File

@@ -39,6 +39,12 @@
#include <QDebug>
#include <QtCore/qmetaobject.h>
#include <private/qgstutils_p.h>
#if !GST_CHECK_VERSION(1,0,0)
typedef GstFocusMode GstPhotographyFocusMode;
#endif
//#define CAMERABIN_DEBUG 1
QT_BEGIN_NAMESPACE
@@ -73,7 +79,7 @@ QCameraFocus::FocusModes CameraBinFocus::focusMode() const
void CameraBinFocus::setFocusMode(QCameraFocus::FocusModes mode)
{
GstFocusMode photographyMode;
GstPhotographyFocusMode photographyMode;
switch (mode) {
case QCameraFocus::AutoFocus:
@@ -181,9 +187,10 @@ QCameraFocusZoneList CameraBinFocus::focusZones() const
void CameraBinFocus::handleFocusMessage(GstMessage *gm)
{
//it's a sync message, so it's called from non main thread
if (gst_structure_has_name(gm->structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) {
const GstStructure *structure = gst_message_get_structure(gm);
if (gst_structure_has_name(structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) {
gint status = GST_PHOTOGRAPHY_FOCUS_STATUS_NONE;
gst_structure_get_int (gm->structure, "status", &status);
gst_structure_get_int (structure, "status", &status);
QCamera::LockStatus focusStatus = m_focusStatus;
QCamera::LockChangeReason reason = QCamera::UserRequest;
@@ -243,7 +250,7 @@ void CameraBinFocus::_q_handleCameraStateChange(QCamera::State state)
m_cameraState = state;
if (state == QCamera::ActiveState) {
if (GstPad *pad = gst_element_get_static_pad(m_session->cameraSource(), "vfsrc")) {
if (GstCaps *caps = gst_pad_get_negotiated_caps(pad)) {
if (GstCaps *caps = qt_gst_pad_get_current_caps(pad)) {
if (GstStructure *structure = gst_caps_get_structure(caps, 0)) {
int width = 0;
int height = 0;

View File

@@ -53,11 +53,13 @@ QT_BEGIN_NAMESPACE
CameraBinImageCapture::CameraBinImageCapture(CameraBinSession *session)
:QCameraImageCaptureControl(session)
, m_encoderProbe(this)
, m_muxerProbe(this)
, m_session(session)
, m_ready(false)
, m_requestId(0)
, m_jpegEncoderElement(0)
, m_metadataMuxerElement(0)
, m_requestId(0)
, m_ready(false)
{
connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateState()));
connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int)));
@@ -108,11 +110,18 @@ void CameraBinImageCapture::updateState()
}
}
gboolean CameraBinImageCapture::metadataEventProbe(GstPad *pad, GstEvent *event, CameraBinImageCapture *self)
#if GST_CHECK_VERSION(1,0,0)
GstPadProbeReturn CameraBinImageCapture::encoderEventProbe(
GstPad *, GstPadProbeInfo *info, gpointer user_data)
{
Q_UNUSED(pad);
if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
GstEvent * const event = gst_pad_probe_info_get_event(info);
#else
gboolean CameraBinImageCapture::encoderEventProbe(
GstElement *, GstEvent *event, gpointer user_data)
{
#endif
CameraBinImageCapture * const self = static_cast<CameraBinImageCapture *>(user_data);
if (event && GST_EVENT_TYPE(event) == GST_EVENT_TAG) {
GstTagList *gstTags;
gst_event_parse_tag(event, &gstTags);
QMap<QByteArray, QVariant> extendedTags = QGstUtils::gstTagListToMap(gstTags);
@@ -146,17 +155,31 @@ gboolean CameraBinImageCapture::metadataEventProbe(GstPad *pad, GstEvent *event,
}
}
}
return true;
#if GST_CHECK_VERSION(1,0,0)
return GST_PAD_PROBE_OK;
#else
return TRUE;
#endif
}
gboolean CameraBinImageCapture::uncompressedBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *self)
void CameraBinImageCapture::EncoderProbe::probeCaps(GstCaps *caps)
{
Q_UNUSED(pad);
CameraBinSession *session = self->m_session;
#if GST_CHECK_VERSION(1,0,0)
capture->m_bufferFormat = QGstUtils::formatForCaps(caps, &capture->m_videoInfo);
#else
int bytesPerLine = 0;
QVideoSurfaceFormat format = QGstUtils::formatForCaps(caps, &bytesPerLine);
capture->m_bytesPerLine = bytesPerLine;
capture->m_bufferFormat = format;
#endif
}
bool CameraBinImageCapture::EncoderProbe::probeBuffer(GstBuffer *buffer)
{
CameraBinSession * const session = capture->m_session;
#ifdef DEBUG_CAPTURE
qDebug() << "Uncompressed buffer probe" << gst_caps_to_string(GST_BUFFER_CAPS(buffer));
qDebug() << "Uncompressed buffer probe";
#endif
QCameraImageCapture::CaptureDestinations destination =
@@ -165,21 +188,23 @@ gboolean CameraBinImageCapture::uncompressedBufferProbe(GstPad *pad, GstBuffer *
if (destination & QCameraImageCapture::CaptureToBuffer) {
if (format != QVideoFrame::Format_Jpeg) {
GstCaps *caps = GST_BUFFER_CAPS(buffer);
int bytesPerLine = -1;
QVideoSurfaceFormat format = QVideoSurfaceGstSink::formatForCaps(caps, &bytesPerLine);
#ifdef DEBUG_CAPTURE
qDebug() << "imageAvailable(uncompressed):" << format;
#endif
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, bytesPerLine);
#if GST_CHECK_VERSION(1,0,0)
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, capture->m_videoInfo);
#else
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, capture->m_bytesPerLine);
#endif
QVideoFrame frame(videoBuffer,
format.frameSize(),
format.pixelFormat());
QVideoFrame frame(
videoBuffer,
capture->m_bufferFormat.frameSize(),
capture->m_bufferFormat.pixelFormat());
QMetaObject::invokeMethod(self, "imageAvailable",
QMetaObject::invokeMethod(capture, "imageAvailable",
Qt::QueuedConnection,
Q_ARG(int, self->m_requestId),
Q_ARG(int, capture->m_requestId),
Q_ARG(QVideoFrame, frame));
}
}
@@ -192,25 +217,40 @@ gboolean CameraBinImageCapture::uncompressedBufferProbe(GstPad *pad, GstBuffer *
return keepBuffer;
}
gboolean CameraBinImageCapture::jpegBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *self)
void CameraBinImageCapture::MuxerProbe::probeCaps(GstCaps *caps)
{
Q_UNUSED(pad);
CameraBinSession *session = self->m_session;
capture->m_jpegResolution = QGstUtils::capsCorrectedResolution(caps);
}
#ifdef DEBUG_CAPTURE
qDebug() << "Jpeg buffer probe" << gst_caps_to_string(GST_BUFFER_CAPS(buffer));
#endif
bool CameraBinImageCapture::MuxerProbe::probeBuffer(GstBuffer *buffer)
{
CameraBinSession * const session = capture->m_session;
QCameraImageCapture::CaptureDestinations destination =
session->captureDestinationControl()->captureDestination();
if ((destination & QCameraImageCapture::CaptureToBuffer) &&
session->captureBufferFormatControl()->bufferFormat() == QVideoFrame::Format_Jpeg) {
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer,
-1); //bytesPerLine is not available for jpegs
QSize resolution = QGstUtils::capsCorrectedResolution(GST_BUFFER_CAPS(buffer));
QSize resolution = capture->m_jpegResolution;
//if resolution is not presented in caps, try to find it from encoded jpeg data:
#if GST_CHECK_VERSION(1,0,0)
GstMapInfo mapInfo;
if (resolution.isEmpty() && gst_buffer_map(buffer, &mapInfo, GST_MAP_READ)) {
QBuffer data;
data.setData(reinterpret_cast<const char*>(mapInfo.data), mapInfo.size);
QImageReader reader(&data, "JPEG");
resolution = reader.size();
gst_buffer_unmap(buffer, &mapInfo);
}
GstVideoInfo info;
gst_video_info_set_format(
&info, GST_VIDEO_FORMAT_ENCODED, resolution.width(), resolution.height());
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer, info);
#else
if (resolution.isEmpty()) {
QBuffer data;
data.setData(reinterpret_cast<const char*>(GST_BUFFER_DATA(buffer)), GST_BUFFER_SIZE(buffer));
@@ -218,20 +258,28 @@ gboolean CameraBinImageCapture::jpegBufferProbe(GstPad *pad, GstBuffer *buffer,
resolution = reader.size();
}
QGstVideoBuffer *videoBuffer = new QGstVideoBuffer(buffer,
-1); //bytesPerLine is not available for jpegs
#endif
QVideoFrame frame(videoBuffer,
resolution,
QVideoFrame::Format_Jpeg);
QMetaObject::invokeMethod(self, "imageAvailable",
QMetaObject::invokeMethod(capture, "imageAvailable",
Qt::QueuedConnection,
Q_ARG(int, self->m_requestId),
Q_ARG(int, capture->m_requestId),
Q_ARG(QVideoFrame, frame));
}
//drop the buffer if capture to file was disabled
return destination & QCameraImageCapture::CaptureToFile;
// Theoretically we could drop the buffer here when don't want to capture to file but that
// prevents camerabin from recognizing that capture has been completed and returning
// to its idle state.
return true;
}
bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message)
{
//Install metadata event and buffer probes
@@ -252,9 +300,10 @@ bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message)
return false;
QString elementName = QString::fromLatin1(gst_element_get_name(element));
#if !GST_CHECK_VERSION(1,0,0)
GstElementClass *elementClass = GST_ELEMENT_GET_CLASS(element);
QString elementLongName = elementClass->details.longname;
#endif
if (elementName.contains("jpegenc") && element != m_jpegEncoderElement) {
m_jpegEncoderElement = element;
GstPad *sinkpad = gst_element_get_static_pad(element, "sink");
@@ -264,21 +313,23 @@ bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message)
#ifdef DEBUG_CAPTURE
qDebug() << "install metadata probe";
#endif
gst_pad_add_event_probe(sinkpad,
G_CALLBACK(CameraBinImageCapture::metadataEventProbe),
this);
#if GST_CHECK_VERSION(1,0,0)
gst_pad_add_probe(
sinkpad, GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, encoderEventProbe, this, NULL);
#else
gst_pad_add_event_probe(sinkpad, G_CALLBACK(encoderEventProbe), this);
#endif
#ifdef DEBUG_CAPTURE
qDebug() << "install uncompressed buffer probe";
#endif
gst_pad_add_buffer_probe(sinkpad,
G_CALLBACK(CameraBinImageCapture::uncompressedBufferProbe),
this);
m_encoderProbe.addProbeToPad(sinkpad, true);
gst_object_unref(sinkpad);
} else if ((elementName.contains("jifmux") ||
elementName.startsWith("metadatamux") ||
elementLongName == QLatin1String("JPEG stream muxer"))
} else if ((elementName.contains("jifmux")
#if !GST_CHECK_VERSION(1,0,0)
|| elementLongName == QLatin1String("JPEG stream muxer")
#endif
|| elementName.startsWith("metadatamux"))
&& element != m_metadataMuxerElement) {
//Jpeg encoded buffer probe is added after jifmux/metadatamux
//element to ensure the resulting jpeg buffer contains capture metadata
@@ -288,9 +339,8 @@ bool CameraBinImageCapture::processBusMessage(const QGstreamerMessage &message)
#ifdef DEBUG_CAPTURE
qDebug() << "install jpeg buffer probe";
#endif
gst_pad_add_buffer_probe(srcpad,
G_CALLBACK(CameraBinImageCapture::jpegBufferProbe),
this);
m_muxerProbe.addProbeToPad(srcpad);
gst_object_unref(srcpad);
}
}

View File

@@ -38,6 +38,14 @@
#include <qcameraimagecapturecontrol.h>
#include "camerabinsession.h"
#include <qvideosurfaceformat.h>
#include <private/qgstreamerbufferprobe_p.h>
#if GST_CHECK_VERSION(1,0,0)
#include <gst/video/video.h>
#endif
QT_BEGIN_NAMESPACE
class CameraBinImageCapture : public QCameraImageCaptureControl, public QGstreamerBusMessageFilter
@@ -61,15 +69,47 @@ private slots:
void updateState();
private:
static gboolean metadataEventProbe(GstPad *pad, GstEvent *event, CameraBinImageCapture *);
static gboolean uncompressedBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *);
static gboolean jpegBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *);
#if GST_CHECK_VERSION(1,0,0)
static GstPadProbeReturn encoderEventProbe(GstPad *, GstPadProbeInfo *info, gpointer user_data);
#else
static gboolean encoderEventProbe(GstElement *, GstEvent *event, gpointer user_data);
#endif
class EncoderProbe : public QGstreamerBufferProbe
{
public:
EncoderProbe(CameraBinImageCapture *capture) : capture(capture) {}
void probeCaps(GstCaps *caps);
bool probeBuffer(GstBuffer *buffer);
private:
CameraBinImageCapture * const capture;
} m_encoderProbe;
class MuxerProbe : public QGstreamerBufferProbe
{
public:
MuxerProbe(CameraBinImageCapture *capture) : capture(capture) {}
void probeCaps(GstCaps *caps);
bool probeBuffer(GstBuffer *buffer);
private:
CameraBinImageCapture * const capture;
} m_muxerProbe;
QVideoSurfaceFormat m_bufferFormat;
QSize m_jpegResolution;
CameraBinSession *m_session;
bool m_ready;
int m_requestId;
GstElement *m_jpegEncoderElement;
GstElement *m_metadataMuxerElement;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo m_videoInfo;
#else
int m_bytesPerLine;
#endif
int m_requestId;
bool m_ready;
};
QT_END_NAMESPACE

View File

@@ -49,7 +49,6 @@ CameraBinImageEncoder::~CameraBinImageEncoder()
QList<QSize> CameraBinImageEncoder::supportedResolutions(const QImageEncoderSettings &, bool *continuous) const
{
qDebug() << "CameraBinImageEncoder::supportedResolutions()";
if (continuous)
*continuous = false;

View File

@@ -34,7 +34,11 @@
#include "camerabinimageprocessing.h"
#include "camerabinsession.h"
#include <gst/interfaces/colorbalance.h>
#if GST_CHECK_VERSION(1,0,0)
# include <gst/video/colorbalance.h>
#else
# include <gst/interfaces/colorbalance.h>
#endif
QT_BEGIN_NAMESPACE
@@ -126,7 +130,7 @@ bool CameraBinImageProcessing::setColorBalanceValue(const QString& channel, qrea
QCameraImageProcessing::WhiteBalanceMode CameraBinImageProcessing::whiteBalanceMode() const
{
#ifdef HAVE_GST_PHOTOGRAPHY
GstWhiteBalanceMode wbMode;
GstPhotographyWhiteBalanceMode wbMode;
gst_photography_get_white_balance_mode(m_session->photography(), &wbMode);
return m_mappedWbValues[wbMode];
#else

View File

@@ -41,7 +41,10 @@
#include <glib.h>
#ifdef HAVE_GST_PHOTOGRAPHY
#include <gst/interfaces/photography.h>
# include <gst/interfaces/photography.h>
# if !GST_CHECK_VERSION(1,0,0)
typedef GstWhiteBalanceMode GstPhotographyWhiteBalanceMode;
# endif
#endif
QT_BEGIN_NAMESPACE
@@ -73,7 +76,7 @@ private:
CameraBinSession *m_session;
QMap<QCameraImageProcessingControl::ProcessingParameter, int> m_values;
#ifdef HAVE_GST_PHOTOGRAPHY
QMap<GstWhiteBalanceMode, QCameraImageProcessing::WhiteBalanceMode> m_mappedWbValues;
QMap<GstPhotographyWhiteBalanceMode, QCameraImageProcessing::WhiteBalanceMode> m_mappedWbValues;
#endif
};

View File

@@ -126,7 +126,7 @@ static const QGStreamerMetaDataKeys *qt_gstreamerMetaDataKeys()
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AlbumTitle, GST_TAG_ALBUM, QVariant::String));
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::AlbumArtist, GST_TAG_ARTIST, QVariant::String));
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::ContributingArtist, GST_TAG_PERFORMER, QVariant::String));
#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19)
#if GST_CHECK_VERSION(0,10,19)
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Composer, GST_TAG_COMPOSER, QVariant::String));
#endif
//metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Conductor, 0, QVariant::String));
@@ -153,8 +153,7 @@ static const QGStreamerMetaDataKeys *qt_gstreamerMetaDataKeys()
//metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Director, 0, QVariant::String));
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::LeadPerformer, GST_TAG_PERFORMER, QVariant::String));
//metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::Writer, 0, QVariant::String));
#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 30)
#if GST_CHECK_VERSION(0,10,30)
// Photos
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CameraManufacturer, GST_TAG_DEVICE_MANUFACTURER, QVariant::String));
metadataKeys->append(QGStreamerMetaDataKey(QMediaMetaData::CameraModel, GST_TAG_DEVICE_MODEL, QVariant::String));

View File

@@ -110,9 +110,10 @@ void CameraBinRecorder::updateStatus()
m_state = QMediaRecorder::StoppedState;
m_session->stopVideoRecording();
}
m_status = m_session->pendingState() == QCamera::ActiveState ?
QMediaRecorder::LoadingStatus :
QMediaRecorder::UnloadedStatus;
m_status = m_session->pendingState() == QCamera::ActiveState
&& m_session->captureMode().testFlag(QCamera::CaptureVideo)
? QMediaRecorder::LoadingStatus
: QMediaRecorder::UnloadedStatus;
}
if (m_state != oldState)
@@ -161,8 +162,6 @@ void CameraBinRecorder::applySettings()
QVideoEncoderSettings videoSettings = videoEncoderControl->videoSettings();
videoSettings.setCodec(candidate[1]);
if (videoSettings.resolution().isEmpty())
videoSettings.setResolution(640, 480);
videoEncoderControl->setActualVideoSettings(videoSettings);
QAudioEncoderSettings audioSettings = audioEncoderControl->audioSettings();

View File

@@ -56,11 +56,11 @@
#include "camerabincapturedestination.h"
#include "camerabinviewfindersettings.h"
#include <private/qgstreamerbushelper_p.h>
#include <private/qgstutils_p.h>
#include <private/qgstreameraudioinputselector_p.h>
#include <private/qgstreamervideoinputdevicecontrol_p.h>
#if defined(HAVE_WIDGETS)
#include <private/qgstreamervideowidget_p.h>
#endif
@@ -121,7 +121,6 @@ CameraBinService::CameraBinService(GstElementFactory *sourceFactory, QObject *pa
#else
m_videoWindow = new QGstreamerVideoWindow(this);
#endif
#if defined(HAVE_WIDGETS)
m_videoWidgetControl = new QGstreamerVideoWidgetControl(this);
#endif
@@ -150,8 +149,6 @@ QMediaControl *CameraBinService::requestControl(const char *name)
if (!m_captureSession)
return 0;
//qDebug() << "Request control" << name;
if (!m_videoOutput) {
if (qstrcmp(name, QVideoRendererControl_iid) == 0) {
m_videoOutput = m_videoRenderer;
@@ -249,7 +246,7 @@ void CameraBinService::releaseControl(QMediaControl *control)
bool CameraBinService::isCameraBinAvailable()
{
GstElementFactory *factory = gst_element_factory_find("camerabin2");
GstElementFactory *factory = gst_element_factory_find(QT_GSTREAMER_CAMERABIN_ELEMENT_NAME);
if (factory) {
gst_object_unref(GST_OBJECT(factory));
return true;

View File

@@ -140,8 +140,8 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa
{
if (m_sourceFactory)
gst_object_ref(GST_OBJECT(m_sourceFactory));
m_camerabin = gst_element_factory_make(QT_GSTREAMER_CAMERABIN_ELEMENT_NAME, "camerabin");
m_camerabin = gst_element_factory_make("camerabin2", "camerabin2");
g_signal_connect(G_OBJECT(m_camerabin), "notify::idle", G_CALLBACK(updateBusyStatus), this);
g_signal_connect(G_OBJECT(m_camerabin), "element-added", G_CALLBACK(elementAdded), this);
g_signal_connect(G_OBJECT(m_camerabin), "element-removed", G_CALLBACK(elementRemoved), this);
@@ -178,7 +178,15 @@ CameraBinSession::CameraBinSession(GstElementFactory *sourceFactory, QObject *pa
//post image preview in RGB format
g_object_set(G_OBJECT(m_camerabin), POST_PREVIEWS_PROPERTY, TRUE, NULL);
#if GST_CHECK_VERSION(1,0,0)
GstCaps *previewCaps = gst_caps_new_simple(
"video/x-raw",
"format", G_TYPE_STRING, "RGBx",
NULL);
#else
GstCaps *previewCaps = gst_caps_from_string("video/x-raw-rgb");
#endif
g_object_set(G_OBJECT(m_camerabin), PREVIEW_CAPS_PROPERTY, previewCaps, NULL);
gst_caps_unref(previewCaps);
}
@@ -243,6 +251,7 @@ bool CameraBinSession::setupCameraBin()
qWarning() << "Staring camera without viewfinder available";
m_viewfinderElement = gst_element_factory_make("fakesink", NULL);
}
g_object_set(G_OBJECT(m_viewfinderElement), "sync", FALSE, NULL);
qt_gst_object_ref_sink(GST_OBJECT(m_viewfinderElement));
gst_element_set_state(m_camerabin, GST_STATE_NULL);
g_object_set(G_OBJECT(m_camerabin), VIEWFINDER_SINK_PROPERTY, m_viewfinderElement, NULL);
@@ -251,61 +260,27 @@ bool CameraBinSession::setupCameraBin()
return true;
}
static GstCaps *resolutionToCaps(const QSize &resolution, const QPair<int, int> &rate = qMakePair<int,int>(0,0))
static GstCaps *resolutionToCaps(const QSize &resolution, qreal frameRate = 0.0)
{
if (resolution.isEmpty())
return gst_caps_new_any();
GstCaps *caps = QGstUtils::videoFilterCaps();
GstCaps *caps = 0;
if (rate.second > 0) {
caps = gst_caps_new_full(gst_structure_new("video/x-raw-yuv",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL),
gst_structure_new("video/x-raw-rgb",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL),
gst_structure_new("video/x-raw-data",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL),
gst_structure_new("video/x-android-buffer",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL),
gst_structure_new("image/jpeg",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL),
NULL);
} else {
caps = gst_caps_new_full (gst_structure_new ("video/x-raw-yuv",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL),
gst_structure_new ("video/x-raw-rgb",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL),
gst_structure_new("video/x-raw-data",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL),
gst_structure_new ("video/x-android-buffer",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL),
gst_structure_new ("image/jpeg",
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL),
NULL);
if (!resolution.isEmpty()) {
gst_caps_set_simple(
caps,
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT, resolution.height(),
NULL);
}
if (frameRate > 0.0) {
gint numerator;
gint denominator;
gst_util_double_to_fraction(frameRate, &numerator, &denominator);
gst_caps_set_simple(
caps,
"framerate", GST_TYPE_FRACTION, numerator, denominator,
NULL);
}
return caps;
@@ -314,40 +289,40 @@ static GstCaps *resolutionToCaps(const QSize &resolution, const QPair<int, int>
void CameraBinSession::setupCaptureResolution()
{
QSize resolution = m_imageEncodeControl->imageSettings().resolution();
if (!resolution.isEmpty()) {
{
GstCaps *caps = resolutionToCaps(resolution);
#if CAMERABIN_DEBUG
qDebug() << Q_FUNC_INFO << "set image resolution" << resolution << gst_caps_to_string(caps);
qDebug() << Q_FUNC_INFO << "set image resolution" << resolution << caps;
#endif
g_object_set(m_camerabin, IMAGE_CAPTURE_CAPS_PROPERTY, caps, NULL);
gst_caps_unref(caps);
} else {
g_object_set(m_camerabin, IMAGE_CAPTURE_CAPS_PROPERTY, NULL, NULL);
if (caps)
gst_caps_unref(caps);
}
const QSize viewfinderResolution = m_viewfinderSettingsControl->resolution();
resolution = m_videoEncodeControl->actualVideoSettings().resolution();
//qreal framerate = m_videoEncodeControl->videoSettings().frameRate();
if (!resolution.isEmpty()) {
GstCaps *caps = resolutionToCaps(resolution /*, framerate*/); //convert to rational
qreal framerate = m_videoEncodeControl->videoSettings().frameRate();
{
GstCaps *caps = resolutionToCaps(
!resolution.isEmpty() ? resolution : viewfinderResolution, framerate);
#if CAMERABIN_DEBUG
qDebug() << Q_FUNC_INFO << "set video resolution" << resolution << gst_caps_to_string(caps);
qDebug() << Q_FUNC_INFO << "set video resolution" << resolution << caps;
#endif
g_object_set(m_camerabin, VIDEO_CAPTURE_CAPS_PROPERTY, caps, NULL);
gst_caps_unref(caps);
} else {
g_object_set(m_camerabin, VIDEO_CAPTURE_CAPS_PROPERTY, NULL, NULL);
if (caps)
gst_caps_unref(caps);
}
resolution = m_viewfinderSettingsControl->resolution();
if (!resolution.isEmpty()) {
if (!viewfinderResolution.isEmpty())
resolution = viewfinderResolution;
{
GstCaps *caps = resolutionToCaps(resolution);
#if CAMERABIN_DEBUG
qDebug() << Q_FUNC_INFO << "set viewfinder resolution" << resolution << gst_caps_to_string(caps);
qDebug() << Q_FUNC_INFO << "set viewfinder resolution" << resolution << caps;
#endif
g_object_set(m_camerabin, VIEWFINDER_CAPS_PROPERTY, caps, NULL);
gst_caps_unref(caps);
} else {
g_object_set(m_camerabin, VIEWFINDER_CAPS_PROPERTY, NULL, NULL);
if (caps)
gst_caps_unref(caps);
}
if (m_videoEncoder)
@@ -363,13 +338,17 @@ void CameraBinSession::setAudioCaptureCaps()
if (sampleRate == -1 && channelCount == -1)
return;
#if GST_CHECK_VERSION(1,0,0)
GstStructure *structure = gst_structure_new_empty(QT_GSTREAMER_RAW_AUDIO_MIME);
#else
GstStructure *structure = gst_structure_new(
"audio/x-raw-int",
QT_GSTREAMER_RAW_AUDIO_MIME,
"endianness", G_TYPE_INT, 1234,
"signed", G_TYPE_BOOLEAN, TRUE,
"width", G_TYPE_INT, 16,
"depth", G_TYPE_INT, 16,
NULL);
#endif
if (sampleRate != -1)
gst_structure_set(structure, "rate", G_TYPE_INT, sampleRate, NULL);
if (channelCount != -1)
@@ -760,7 +739,7 @@ qint64 CameraBinSession::duration() const
if (fileSink) {
GstFormat format = GST_FORMAT_TIME;
gint64 duration = 0;
bool ret = gst_element_query_position(fileSink, &format, &duration);
bool ret = qt_gst_element_query_position(fileSink, format, &duration);
gst_object_unref(GST_OBJECT(fileSink));
if (ret)
return duration / 1000000;
@@ -795,129 +774,57 @@ void CameraBinSession::setMetaData(const QMap<QByteArray, QVariant> &data)
{
m_metaData = data;
if (m_camerabin) {
GstIterator *elements = gst_bin_iterate_all_by_interface(GST_BIN(m_camerabin), GST_TYPE_TAG_SETTER);
GstElement *element = 0;
while (gst_iterator_next(elements, (void**)&element) == GST_ITERATOR_OK) {
gst_tag_setter_reset_tags(GST_TAG_SETTER(element));
QMapIterator<QByteArray, QVariant> it(data);
while (it.hasNext()) {
it.next();
const QString tagName = it.key();
const QVariant tagValue = it.value();
switch(tagValue.type()) {
case QVariant::String:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toString().toUtf8().constData(),
NULL);
break;
case QVariant::Int:
case QVariant::LongLong:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toInt(),
NULL);
break;
case QVariant::Double:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
tagValue.toDouble(),
NULL);
break;
case QVariant::DateTime: {
QDateTime date = tagValue.toDateTime().toLocalTime();
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE,
tagName.toUtf8().constData(),
gst_date_time_new_local_time(
date.date().year(), date.date().month(), date.date().day(),
date.time().hour(), date.time().minute(), date.time().second()),
NULL);
break;
}
default:
break;
}
}
}
gst_iterator_free(elements);
}
if (m_camerabin)
QGstUtils::setMetaData(m_camerabin, data);
}
bool CameraBinSession::processSyncMessage(const QGstreamerMessage &message)
{
GstMessage* gm = message.rawMessage();
const GstStructure *st;
const GValue *image;
GstBuffer *buffer = NULL;
if (gm && GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) {
if (m_captureMode == QCamera::CaptureStillImage &&
gst_structure_has_name(gm->structure, "preview-image")) {
st = gst_message_get_structure(gm);
if (gst_structure_has_field_typed(st, "buffer", GST_TYPE_BUFFER)) {
image = gst_structure_get_value(st, "buffer");
if (image) {
buffer = gst_value_get_buffer(image);
QImage img;
GstCaps *caps = gst_buffer_get_caps(buffer);
if (caps) {
GstStructure *structure = gst_caps_get_structure(caps, 0);
gint width = 0;
gint height = 0;
#if CAMERABIN_DEBUG
qDebug() << "Preview caps:" << gst_structure_to_string(structure);
const GstStructure *st = gst_message_get_structure(gm);
const GValue *sampleValue = 0;
if (m_captureMode == QCamera::CaptureStillImage
&& gst_structure_has_name(st, "preview-image")
#if GST_CHECK_VERSION(1,0,0)
&& gst_structure_has_field_typed(st, "sample", GST_TYPE_SAMPLE)
&& (sampleValue = gst_structure_get_value(st, "sample"))) {
GstSample * const sample = gst_value_get_sample(sampleValue);
GstCaps * const previewCaps = gst_sample_get_caps(sample);
GstBuffer * const buffer = gst_sample_get_buffer(sample);
#else
&& gst_structure_has_field_typed(st, "buffer", GST_TYPE_BUFFER)
&& (sampleValue = gst_structure_get_value(st, "buffer"))) {
GstBuffer * const buffer = gst_value_get_buffer(sampleValue);
#endif
if (structure &&
gst_structure_get_int(structure, "width", &width) &&
gst_structure_get_int(structure, "height", &height) &&
width > 0 && height > 0) {
if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) {
QImage::Format format = QImage::Format_Invalid;
int bpp = 0;
gst_structure_get_int(structure, "bpp", &bpp);
QImage image;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo previewInfo;
if (gst_video_info_from_caps(&previewInfo, previewCaps))
image = QGstUtils::bufferToImage(buffer, previewInfo);
gst_sample_unref(sample);
#else
image = QGstUtils::bufferToImage(buffer);
gst_buffer_unref(buffer);
#endif
if (!image.isNull()) {
static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageExposed);
exposedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_requestId));
if (bpp == 24)
format = QImage::Format_RGB888;
else if (bpp == 32)
format = QImage::Format_RGB32;
if (format != QImage::Format_Invalid) {
img = QImage((const uchar *)buffer->data, width, height, format);
img.bits(); //detach
}
}
}
gst_caps_unref(caps);
static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageExposed);
exposedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_requestId));
static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageCaptured);
capturedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_requestId),
Q_ARG(QImage,img));
}
}
return true;
static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&CameraBinSession::imageCaptured);
capturedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_requestId),
Q_ARG(QImage,image));
}
return true;
}
#ifdef HAVE_GST_PHOTOGRAPHY
if (gst_structure_has_name(gm->structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE))
if (gst_structure_has_name(st, GST_PHOTOGRAPHY_AUTOFOCUS_DONE))
m_cameraFocusControl->handleFocusMessage(gm);
#endif
}
@@ -1109,20 +1016,12 @@ QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frame
if (frameSize.isEmpty()) {
caps = gst_caps_copy(supportedCaps);
} else {
GstCaps *filter = gst_caps_new_full(
gst_structure_new(
"video/x-raw-rgb",
"width" , G_TYPE_INT , frameSize.width(),
"height" , G_TYPE_INT, frameSize.height(), NULL),
gst_structure_new(
"video/x-raw-yuv",
"width" , G_TYPE_INT, frameSize.width(),
"height" , G_TYPE_INT, frameSize.height(), NULL),
gst_structure_new(
"image/jpeg",
"width" , G_TYPE_INT, frameSize.width(),
"height" , G_TYPE_INT, frameSize.height(), NULL),
NULL);
GstCaps *filter = QGstUtils::videoFilterCaps();
gst_caps_set_simple(
filter,
"width", G_TYPE_INT, frameSize.width(),
"height", G_TYPE_INT, frameSize.height(),
NULL);
caps = gst_caps_intersect(supportedCaps, filter);
gst_caps_unref(filter);
@@ -1133,7 +1032,7 @@ QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frame
caps = gst_caps_make_writable(caps);
for (uint i=0; i<gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
gst_structure_set_name(structure, "video/x-raw-yuv");
gst_structure_set_name(structure, "video/x-raw");
const GValue *oldRate = gst_structure_get_value(structure, "framerate");
GValue rate;
memset(&rate, 0, sizeof(rate));
@@ -1142,8 +1041,11 @@ QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frame
gst_structure_remove_all_fields(structure);
gst_structure_set_value(structure, "framerate", &rate);
}
#if GST_CHECK_VERSION(1,0,0)
caps = gst_caps_simplify(caps);
#else
gst_caps_do_simplify(caps);
#endif
for (uint i=0; i<gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
@@ -1154,7 +1056,7 @@ QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frame
qSort(res.begin(), res.end(), rateLessThan);
#if CAMERABIN_DEBUG
qDebug() << "Supported rates:" << gst_caps_to_string(caps);
qDebug() << "Supported rates:" << caps;
qDebug() << res;
#endif
@@ -1213,31 +1115,24 @@ QList<QSize> CameraBinSession::supportedResolutions(QPair<int,int> rate,
SUPPORTED_IMAGE_CAPTURE_CAPS_PROPERTY : SUPPORTED_VIDEO_CAPTURE_CAPS_PROPERTY,
&supportedCaps, NULL);
#if CAMERABIN_DEBUG
qDebug() << "Source caps:" << supportedCaps;
#endif
if (!supportedCaps)
return res;
#if CAMERABIN_DEBUG
qDebug() << "Source caps:" << gst_caps_to_string(supportedCaps);
#endif
GstCaps *caps = 0;
bool isContinuous = false;
if (rate.first <= 0 || rate.second <= 0) {
caps = gst_caps_copy(supportedCaps);
} else {
GstCaps *filter = gst_caps_new_full(
gst_structure_new(
"video/x-raw-rgb",
"framerate" , GST_TYPE_FRACTION , rate.first, rate.second, NULL),
gst_structure_new(
"video/x-raw-yuv",
"framerate" , GST_TYPE_FRACTION , rate.first, rate.second, NULL),
gst_structure_new(
"image/jpeg",
"framerate" , GST_TYPE_FRACTION , rate.first, rate.second, NULL),
NULL);
GstCaps *filter = QGstUtils::videoFilterCaps();
gst_caps_set_simple(
filter,
"framerate" , GST_TYPE_FRACTION , rate.first, rate.second,
NULL);
caps = gst_caps_intersect(supportedCaps, filter);
gst_caps_unref(filter);
}
@@ -1247,7 +1142,7 @@ QList<QSize> CameraBinSession::supportedResolutions(QPair<int,int> rate,
caps = gst_caps_make_writable(caps);
for (uint i=0; i<gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
gst_structure_set_name(structure, "video/x-raw-yuv");
gst_structure_set_name(structure, "video/x-raw");
const GValue *oldW = gst_structure_get_value(structure, "width");
const GValue *oldH = gst_structure_get_value(structure, "height");
GValue w;
@@ -1262,7 +1157,13 @@ QList<QSize> CameraBinSession::supportedResolutions(QPair<int,int> rate,
gst_structure_set_value(structure, "width", &w);
gst_structure_set_value(structure, "height", &h);
}
#if GST_CHECK_VERSION(1,0,0)
caps = gst_caps_simplify(caps);
#else
gst_caps_do_simplify(caps);
#endif
for (uint i=0; i<gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);

View File

@@ -12,14 +12,18 @@ LIBS += -lqgsttools_p
CONFIG += link_pkgconfig
PKGCONFIG += \
gstreamer-0.10 \
gstreamer-base-0.10 \
gstreamer-interfaces-0.10 \
gstreamer-audio-0.10 \
gstreamer-video-0.10 \
gstreamer-pbutils-0.10
gstreamer-$$GST_VERSION \
gstreamer-base-$$GST_VERSION \
gstreamer-audio-$$GST_VERSION \
gstreamer-video-$$GST_VERSION \
gstreamer-pbutils-$$GST_VERSION
maemo*:PKGCONFIG +=gstreamer-plugins-bad-$$GST_VERSION
mir: {
DEFINES += HAVE_MIR
}
maemo*:PKGCONFIG +=gstreamer-plugins-bad-0.10
config_resourcepolicy {
DEFINES += HAVE_RESOURCE_POLICY
@@ -27,8 +31,8 @@ config_resourcepolicy {
}
config_gstreamer_appsrc {
PKGCONFIG += gstreamer-app-0.10
PKGCONFIG += gstreamer-app-$$GST_VERSION
DEFINES += HAVE_GST_APPSRC
LIBS += -lgstapp-0.10
LIBS += -lgstapp-$$GST_VERSION
}

View File

@@ -2,8 +2,8 @@ TEMPLATE = subdirs
SUBDIRS += \
audiodecoder \
mediacapture \
mediaplayer
mediaplayer \
mediacapture
config_gstreamer_encodingprofiles {
SUBDIRS += camerabin

View File

@@ -1,4 +1,4 @@
{
"Keys": ["gstreamermediacapture"]
"Keys": ["gstreamermediacapture"],
"Services": ["org.qt-project.qt.audiosource", "org.qt-project.qt.camera"]
}

View File

@@ -34,6 +34,7 @@
#include "qgstreameraudioencode.h"
#include "qgstreamercapturesession.h"
#include "qgstreamermediacontainercontrol.h"
#include <private/qgstutils_p.h>
#include <QtCore/qdebug.h>
@@ -175,7 +176,7 @@ GstElement *QGstreamerAudioEncode::createEncoder()
if (m_audioSettings.sampleRate() > 0 || m_audioSettings.channelCount() > 0) {
GstCaps *caps = gst_caps_new_empty();
GstStructure *structure = gst_structure_new("audio/x-raw-int", NULL);
GstStructure *structure = qt_gst_structure_new_empty(QT_GSTREAMER_RAW_AUDIO_MIME);
if (m_audioSettings.sampleRate() > 0)
gst_structure_set(structure, "rate", G_TYPE_INT, m_audioSettings.sampleRate(), NULL );

View File

@@ -62,27 +62,25 @@
QT_BEGIN_NAMESPACE
QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObject *parent):
QMediaService(parent)
{
m_captureSession = 0;
m_cameraControl = 0;
m_metaDataControl = 0;
QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObject *parent)
: QMediaService(parent)
, m_captureSession(0)
, m_cameraControl(0)
#if defined(USE_GSTREAMER_CAMERA)
m_videoInput = 0;
, m_videoInput(0)
#endif
m_audioInputSelector = 0;
m_videoInputDevice = 0;
m_videoOutput = 0;
m_videoRenderer = 0;
m_videoWindow = 0;
, m_metaDataControl(0)
, m_audioInputSelector(0)
, m_videoInputDevice(0)
, m_videoOutput(0)
, m_videoRenderer(0)
, m_videoWindow(0)
#if defined(HAVE_WIDGETS)
m_videoWidgetControl = 0;
, m_videoWidgetControl(0)
#endif
m_imageCaptureControl = 0;
, m_imageCaptureControl(0)
, m_audioProbeControl(0)
{
if (service == Q_MEDIASERVICE_AUDIOSOURCE) {
m_captureSession = new QGstreamerCaptureSession(QGstreamerCaptureSession::Audio, this);
}
@@ -163,12 +161,12 @@ QMediaControl *QGstreamerCaptureService::requestControl(const char *name)
return m_imageCaptureControl;
if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) {
if (m_captureSession) {
QGstreamerAudioProbeControl *probe = new QGstreamerAudioProbeControl(this);
m_captureSession->addProbe(probe);
return probe;
if (!m_audioProbeControl) {
m_audioProbeControl = new QGstreamerAudioProbeControl(this);
m_captureSession->addProbe(m_audioProbeControl);
}
return 0;
m_audioProbeControl->ref.ref();
return m_audioProbeControl;
}
if (!m_videoOutput) {
@@ -194,17 +192,15 @@ QMediaControl *QGstreamerCaptureService::requestControl(const char *name)
void QGstreamerCaptureService::releaseControl(QMediaControl *control)
{
if (control && control == m_videoOutput) {
if (!control) {
return;
} else if (control == m_videoOutput) {
m_videoOutput = 0;
m_captureSession->setVideoPreview(0);
}
QGstreamerAudioProbeControl* audioProbe = qobject_cast<QGstreamerAudioProbeControl*>(control);
if (audioProbe) {
if (m_captureSession)
m_captureSession->removeProbe(audioProbe);
delete audioProbe;
return;
} else if (control == m_audioProbeControl && !m_audioProbeControl->ref.deref()) {
m_captureSession->removeProbe(m_audioProbeControl);
delete m_audioProbeControl;
m_audioProbeControl = 0;
}
}

View File

@@ -43,6 +43,7 @@ QT_BEGIN_NAMESPACE
class QAudioInputSelectorControl;
class QVideoDeviceSelectorControl;
class QGstreamerAudioProbeControl;
class QGstreamerCaptureSession;
class QGstreamerCameraControl;
class QGstreamerMessage;
@@ -86,6 +87,8 @@ private:
QMediaControl *m_videoWidgetControl;
#endif
QGstreamerImageCaptureControl *m_imageCaptureControl;
QGstreamerAudioProbeControl *m_audioProbeControl;
};
QT_END_NAMESPACE

View File

@@ -110,90 +110,16 @@ QMultimedia::SupportEstimate QGstreamerCaptureServicePlugin::hasSupport(const QS
return QGstUtils::hasSupport(mimeType, codecs, m_supportedMimeTypeSet);
}
static bool isEncoderOrMuxer(GstElementFactory *factory)
{
return gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_MUXER)
|| gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_ENCODER);
}
void QGstreamerCaptureServicePlugin::updateSupportedMimeTypes() const
{
//enumerate supported mime types
gst_init(NULL, NULL);
GList *plugins, *orig_plugins;
orig_plugins = plugins = gst_default_registry_get_plugin_list ();
while (plugins) {
GList *features, *orig_features;
GstPlugin *plugin = (GstPlugin *) (plugins->data);
plugins = g_list_next (plugins);
if (plugin->flags & (1<<1)) //GST_PLUGIN_FLAG_BLACKLISTED
continue;
orig_features = features = gst_registry_get_feature_list_by_plugin(gst_registry_get_default (),
plugin->desc.name);
while (features) {
if (!G_UNLIKELY(features->data == NULL)) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE(features->data);
if (GST_IS_ELEMENT_FACTORY (feature)) {
GstElementFactory *factory = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature));
if (factory
&& factory->numpadtemplates > 0
&& (qstrcmp(factory->details.klass, "Codec/Decoder/Audio") == 0
|| qstrcmp(factory->details.klass, "Codec/Decoder/Video") == 0
|| qstrcmp(factory->details.klass, "Codec/Demux") == 0 )) {
const GList *pads = factory->staticpadtemplates;
while (pads) {
GstStaticPadTemplate *padtemplate = (GstStaticPadTemplate*)(pads->data);
pads = g_list_next (pads);
if (padtemplate->direction != GST_PAD_SINK)
continue;
if (padtemplate->static_caps.string) {
GstCaps *caps = gst_static_caps_get(&padtemplate->static_caps);
if (!gst_caps_is_any (caps) && ! gst_caps_is_empty (caps)) {
for (guint i = 0; i < gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
QString nameLowcase = QString(gst_structure_get_name (structure)).toLower();
m_supportedMimeTypeSet.insert(nameLowcase);
if (nameLowcase.contains("mpeg")) {
//Because mpeg version number is only included in the detail
//description, it is necessary to manually extract this information
//in order to match the mime type of mpeg4.
const GValue *value = gst_structure_get_value(structure, "mpegversion");
if (value) {
gchar *str = gst_value_serialize (value);
QString versions(str);
QStringList elements = versions.split(QRegExp("\\D+"), QString::SkipEmptyParts);
foreach (const QString &e, elements)
m_supportedMimeTypeSet.insert(nameLowcase + e);
g_free (str);
}
}
}
}
gst_caps_unref(caps);
}
}
gst_object_unref (factory);
}
} else if (GST_IS_TYPE_FIND_FACTORY(feature)) {
QString name(gst_plugin_feature_get_name(feature));
if (name.contains('/')) //filter out any string without '/' which is obviously not a mime type
m_supportedMimeTypeSet.insert(name.toLower());
}
}
features = g_list_next (features);
}
gst_plugin_feature_list_free (orig_features);
}
gst_plugin_list_free (orig_plugins);
#if defined QT_SUPPORTEDMIMETYPES_DEBUG
QStringList list = m_supportedMimeTypeSet.toList();
list.sort();
if (qgetenv("QT_DEBUG_PLUGINS").toInt() > 0) {
foreach (const QString &type, list)
qDebug() << type;
}
#endif
m_supportedMimeTypeSet = QGstUtils::supportedMimeTypes(isEncoderOrMuxer);
}
QStringList QGstreamerCaptureServicePlugin::supportedMimeTypes() const

View File

@@ -45,6 +45,7 @@
#include <gst/gsttagsetter.h>
#include <gst/gstversion.h>
#include <gst/video/video.h>
#include <QtCore/qdebug.h>
#include <QtCore/qurl.h>
@@ -52,7 +53,6 @@
#include <QCoreApplication>
#include <QtCore/qmetaobject.h>
#include <QtCore/qfile.h>
#include <QtGui/qimage.h>
QT_BEGIN_NAMESPACE
@@ -64,7 +64,7 @@ QGstreamerCaptureSession::QGstreamerCaptureSession(QGstreamerCaptureSession::Cap
m_waitingForEos(false),
m_pipelineMode(EmptyPipeline),
m_captureMode(captureMode),
m_audioBufferProbeId(-1),
m_audioProbe(0),
m_audioInputFactory(0),
m_audioPreviewFactory(0),
m_videoInputFactory(0),
@@ -169,7 +169,7 @@ GstElement *QGstreamerCaptureSession::buildEncodeBin()
if (m_captureMode & Video) {
GstElement *videoQueue = gst_element_factory_make("queue", "video-encode-queue");
GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-encoder");
GstElement *colorspace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "videoconvert-encoder");
GstElement *videoscale = gst_element_factory_make("videoscale","videoscale-encoder");
gst_bin_add_many(GST_BIN(encodeBin), videoQueue, colorspace, videoscale, NULL);
@@ -280,7 +280,7 @@ GstElement *QGstreamerCaptureSession::buildVideoPreview()
if (m_viewfinderInterface) {
GstElement *bin = gst_bin_new("video-preview-bin");
GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-preview");
GstElement *colorspace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "videoconvert-preview");
GstElement *capsFilter = gst_element_factory_make("capsfilter", "capsfilter-video-preview");
GstElement *preview = m_viewfinderInterface->videoSink();
@@ -299,36 +299,25 @@ GstElement *QGstreamerCaptureSession::buildVideoPreview()
resolution = m_imageEncodeControl->imageSettings().resolution();
}
if (!resolution.isEmpty() || frameRate > 0.001) {
GstCaps *caps = gst_caps_new_empty();
QStringList structureTypes;
structureTypes << "video/x-raw-yuv" << "video/x-raw-rgb";
GstCaps *caps = QGstUtils::videoFilterCaps();
foreach(const QString &structureType, structureTypes) {
GstStructure *structure = gst_structure_new(structureType.toLatin1().constData(), NULL);
if (!resolution.isEmpty()) {
gst_structure_set(structure, "width", G_TYPE_INT, resolution.width(), NULL);
gst_structure_set(structure, "height", G_TYPE_INT, resolution.height(), NULL);
}
if (frameRate > 0.001) {
QPair<int,int> rate = m_videoEncodeControl->rateAsRational();
//qDebug() << "frame rate:" << num << denum;
gst_structure_set(structure, "framerate", GST_TYPE_FRACTION, rate.first, rate.second, NULL);
}
gst_caps_append_structure(caps,structure);
}
//qDebug() << "set video preview caps filter:" << gst_caps_to_string(caps);
g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL);
gst_caps_unref(caps);
if (!resolution.isEmpty()) {
gst_caps_set_simple(caps, "width", G_TYPE_INT, resolution.width(), NULL);
gst_caps_set_simple(caps, "height", G_TYPE_INT, resolution.height(), NULL);
}
if (frameRate > 0.001) {
QPair<int,int> rate = m_videoEncodeControl->rateAsRational();
//qDebug() << "frame rate:" << num << denum;
gst_caps_set_simple(caps, "framerate", GST_TYPE_FRACTION, rate.first, rate.second, NULL);
}
//qDebug() << "set video preview caps filter:" << gst_caps_to_string(caps);
g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL);
gst_caps_unref(caps);
// add ghostpads
GstPad *pad = gst_element_get_static_pad(colorspace, "sink");
@@ -342,7 +331,7 @@ GstElement *QGstreamerCaptureSession::buildVideoPreview()
previewElement = gst_element_factory_make("fakesink", "video-preview");
#else
GstElement *bin = gst_bin_new("video-preview-bin");
GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-preview");
GstElement *colorspace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "videoconvert-preview");
GstElement *preview = gst_element_factory_make("ximagesink", "video-preview");
gst_bin_add_many(GST_BIN(bin), colorspace, preview, NULL);
gst_element_link(colorspace,preview);
@@ -360,101 +349,49 @@ GstElement *QGstreamerCaptureSession::buildVideoPreview()
return previewElement;
}
static gboolean passImageFilter(GstElement *element,
GstBuffer *buffer,
void *appdata)
void QGstreamerCaptureSession::probeCaps(GstCaps *caps)
{
Q_UNUSED(element);
Q_UNUSED(buffer);
#if GST_CHECK_VERSION(1,0,0)
gst_video_info_from_caps(&m_previewInfo, caps);
#else
Q_UNUSED(caps);
#endif
}
QGstreamerCaptureSession *session = (QGstreamerCaptureSession *)appdata;
if (session->m_passImage || session->m_passPrerollImage) {
session->m_passImage = false;
bool QGstreamerCaptureSession::probeBuffer(GstBuffer *buffer)
{
if (m_passPrerollImage) {
m_passImage = false;
m_passPrerollImage = false;
if (session->m_passPrerollImage) {
session->m_passPrerollImage = false;
return TRUE;
}
session->m_passPrerollImage = false;
QImage img;
GstCaps *caps = gst_buffer_get_caps(buffer);
if (caps) {
GstStructure *structure = gst_caps_get_structure (caps, 0);
gint width = 0;
gint height = 0;
if (structure &&
gst_structure_get_int(structure, "width", &width) &&
gst_structure_get_int(structure, "height", &height) &&
width > 0 && height > 0) {
if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-yuv") == 0) {
guint32 fourcc = 0;
gst_structure_get_fourcc(structure, "format", &fourcc);
if (fourcc == GST_MAKE_FOURCC('I','4','2','0')) {
img = QImage(width/2, height/2, QImage::Format_RGB32);
const uchar *data = (const uchar *)buffer->data;
for (int y=0; y<height; y+=2) {
const uchar *yLine = data + y*width;
const uchar *uLine = data + width*height + y*width/4;
const uchar *vLine = data + width*height*5/4 + y*width/4;
for (int x=0; x<width; x+=2) {
const qreal Y = 1.164*(yLine[x]-16);
const int U = uLine[x/2]-128;
const int V = vLine[x/2]-128;
int b = qBound(0, int(Y + 2.018*U), 255);
int g = qBound(0, int(Y - 0.813*V - 0.391*U), 255);
int r = qBound(0, int(Y + 1.596*V), 255);
img.setPixel(x/2,y/2,qRgb(r,g,b));
}
}
}
} else if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) {
QImage::Format format = QImage::Format_Invalid;
int bpp = 0;
gst_structure_get_int(structure, "bpp", &bpp);
if (bpp == 24)
format = QImage::Format_RGB888;
else if (bpp == 32)
format = QImage::Format_RGB32;
if (format != QImage::Format_Invalid) {
img = QImage((const uchar *)buffer->data,
width,
height,
format);
img.bits(); //detach
}
}
}
gst_caps_unref(caps);
}
static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageExposed);
exposedSignal.invoke(session,
Qt::QueuedConnection,
Q_ARG(int,session->m_imageRequestId));
static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageCaptured);
capturedSignal.invoke(session,
Qt::QueuedConnection,
Q_ARG(int,session->m_imageRequestId),
Q_ARG(QImage,img));
return TRUE;
} else {
return FALSE;
return true;
} else if (!m_passImage) {
return false;
}
m_passImage = false;
#if GST_CHECK_VERSION(1,0,0)
QImage img = QGstUtils::bufferToImage(buffer, m_previewInfo);
#else
QImage img = QGstUtils::bufferToImage(buffer);
#endif
if (img.isNull())
return true;
static QMetaMethod exposedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageExposed);
exposedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_imageRequestId));
static QMetaMethod capturedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageCaptured);
capturedSignal.invoke(this,
Qt::QueuedConnection,
Q_ARG(int,m_imageRequestId),
Q_ARG(QImage,img));
return true;
}
static gboolean saveImageFilter(GstElement *element,
@@ -471,7 +408,15 @@ static gboolean saveImageFilter(GstElement *element,
if (!fileName.isEmpty()) {
QFile f(fileName);
if (f.open(QFile::WriteOnly)) {
f.write((const char *)buffer->data, buffer->size);
#if GST_CHECK_VERSION(1,0,0)
GstMapInfo info;
if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
f.write(reinterpret_cast<const char *>(info.data), info.size);
gst_buffer_unmap(buffer, &info);
}
#else
f.write(reinterpret_cast<const char *>(buffer->data), buffer->size);
#endif
f.close();
static QMetaMethod savedSignal = QMetaMethod::fromSignal(&QGstreamerCaptureSession::imageSaved);
@@ -489,18 +434,19 @@ GstElement *QGstreamerCaptureSession::buildImageCapture()
{
GstElement *bin = gst_bin_new("image-capture-bin");
GstElement *queue = gst_element_factory_make("queue", "queue-image-capture");
GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-image-capture");
GstElement *colorspace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "videoconvert-image-capture");
GstElement *encoder = gst_element_factory_make("jpegenc", "image-encoder");
GstElement *sink = gst_element_factory_make("fakesink","sink-image-capture");
GstPad *pad = gst_element_get_static_pad(queue, "src");
Q_ASSERT(pad);
gst_pad_add_buffer_probe(pad, G_CALLBACK(passImageFilter), this);
addProbeToPad(pad, false);
gst_object_unref(GST_OBJECT(pad));
g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL);
g_signal_connect(G_OBJECT(sink), "handoff",
G_CALLBACK(saveImageFilter), this);
g_signal_connect(G_OBJECT(sink), "handoff", G_CALLBACK(saveImageFilter), this);
gst_bin_add_many(GST_BIN(bin), queue, colorspace, encoder, sink, NULL);
gst_element_link_many(queue, colorspace, encoder, sink, NULL);
@@ -715,6 +661,8 @@ void QGstreamerCaptureSession::dumpGraph(const QString &fileName)
_gst_debug_bin_to_dot_file(GST_BIN(m_pipeline),
GstDebugGraphDetails(/*GST_DEBUG_GRAPH_SHOW_ALL |*/ GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES),
fileName.toLatin1());
#else
Q_UNUSED(fileName);
#endif
}
@@ -877,10 +825,8 @@ void QGstreamerCaptureSession::setState(QGstreamerCaptureSession::State newState
qint64 QGstreamerCaptureSession::duration() const
{
GstFormat format = GST_FORMAT_TIME;
gint64 duration = 0;
if ( m_encodeBin && gst_element_query_position(m_encodeBin, &format, &duration))
gint64 duration = 0;
if (m_encodeBin && qt_gst_element_query_position(m_encodeBin, GST_FORMAT_TIME, &duration))
return duration / 1000000;
else
return 0;
@@ -896,50 +842,8 @@ void QGstreamerCaptureSession::setMetaData(const QMap<QByteArray, QVariant> &dat
//qDebug() << "QGstreamerCaptureSession::setMetaData" << data;
m_metaData = data;
if (m_encodeBin) {
GstIterator *elements = gst_bin_iterate_all_by_interface(GST_BIN(m_encodeBin), GST_TYPE_TAG_SETTER);
GstElement *element = 0;
while (gst_iterator_next(elements, (void**)&element) == GST_ITERATOR_OK) {
//qDebug() << "found element with tag setter interface:" << gst_element_get_name(element);
QMapIterator<QByteArray, QVariant> it(data);
while (it.hasNext()) {
it.next();
const QString tagName = it.key();
const QVariant tagValue = it.value();
switch(tagValue.type()) {
case QVariant::String:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE_ALL,
tagName.toUtf8().constData(),
tagValue.toString().toUtf8().constData(),
NULL);
break;
case QVariant::Int:
case QVariant::LongLong:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE_ALL,
tagName.toUtf8().constData(),
tagValue.toInt(),
NULL);
break;
case QVariant::Double:
gst_tag_setter_add_tags(GST_TAG_SETTER(element),
GST_TAG_MERGE_REPLACE_ALL,
tagName.toUtf8().constData(),
tagValue.toDouble(),
NULL);
break;
default:
break;
}
}
}
gst_iterator_free(elements);
}
if (m_encodeBin)
QGstUtils::setMetaData(GST_BIN(m_encodeBin), data);
}
bool QGstreamerCaptureSession::processBusMessage(const QGstreamerMessage &message)
@@ -1058,34 +962,16 @@ void QGstreamerCaptureSession::setVolume(qreal volume)
void QGstreamerCaptureSession::addProbe(QGstreamerAudioProbeControl* probe)
{
QMutexLocker locker(&m_audioProbeMutex);
if (m_audioProbes.contains(probe))
return;
m_audioProbes.append(probe);
Q_ASSERT(!m_audioProbe);
m_audioProbe = probe;
addAudioBufferProbe();
}
void QGstreamerCaptureSession::removeProbe(QGstreamerAudioProbeControl* probe)
{
QMutexLocker locker(&m_audioProbeMutex);
m_audioProbes.removeOne(probe);
}
gboolean QGstreamerCaptureSession::padAudioBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data)
{
Q_UNUSED(pad);
QGstreamerCaptureSession *session = reinterpret_cast<QGstreamerCaptureSession*>(user_data);
QMutexLocker locker(&session->m_audioProbeMutex);
if (session->m_audioProbes.isEmpty())
return TRUE;
foreach (QGstreamerAudioProbeControl* probe, session->m_audioProbes)
probe->bufferProbed(buffer);
return TRUE;
Q_ASSERT(m_audioProbe == probe);
removeAudioBufferProbe();
m_audioProbe = 0;
}
GstPad *QGstreamerCaptureSession::getAudioProbePad()
@@ -1114,26 +1000,25 @@ GstPad *QGstreamerCaptureSession::getAudioProbePad()
void QGstreamerCaptureSession::removeAudioBufferProbe()
{
if (m_audioBufferProbeId == -1)
if (!m_audioProbe)
return;
GstPad *pad = getAudioProbePad();
if (pad) {
gst_pad_remove_buffer_probe(pad, m_audioBufferProbeId);
gst_object_unref(G_OBJECT(pad));
m_audioProbe->removeProbeFromPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
m_audioBufferProbeId = -1;
}
void QGstreamerCaptureSession::addAudioBufferProbe()
{
Q_ASSERT(m_audioBufferProbeId == -1);
if (!m_audioProbe)
return;
GstPad *pad = getAudioProbePad();
if (pad) {
m_audioBufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padAudioBufferProbe), this);
gst_object_unref(G_OBJECT(pad));
m_audioProbe->addProbeToPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
}

View File

@@ -41,8 +41,10 @@
#include <QtCore/qurl.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#include <private/qgstreamerbushelper_p.h>
#include <private/qgstreamerbufferprobe_p.h>
QT_BEGIN_NAMESPACE
@@ -70,7 +72,10 @@ public:
virtual QList<QSize> supportedResolutions(qreal frameRate = -1) const = 0;
};
class QGstreamerCaptureSession : public QObject, public QGstreamerBusMessageFilter
class QGstreamerCaptureSession
: public QObject
, public QGstreamerBusMessageFilter
, private QGstreamerBufferProbe
{
Q_OBJECT
Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged)
@@ -131,7 +136,6 @@ public:
void addProbe(QGstreamerAudioProbeControl* probe);
void removeProbe(QGstreamerAudioProbeControl* probe);
static gboolean padAudioBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data);
signals:
void stateChanged(QGstreamerCaptureSession::State state);
@@ -156,6 +160,9 @@ public slots:
void setVolume(qreal volume);
private:
void probeCaps(GstCaps *caps);
bool probeBuffer(GstBuffer *buffer);
enum PipelineMode { EmptyPipeline, PreviewPipeline, RecordingPipeline, PreviewAndRecordingPipeline };
GstElement *buildEncodeBin();
@@ -180,9 +187,7 @@ private:
QGstreamerCaptureSession::CaptureMode m_captureMode;
QMap<QByteArray, QVariant> m_metaData;
QList<QGstreamerAudioProbeControl*> m_audioProbes;
QMutex m_audioProbeMutex;
int m_audioBufferProbeId;
QGstreamerAudioProbeControl *m_audioProbe;
QGstreamerElementFactory *m_audioInputFactory;
QGstreamerElementFactory *m_audioPreviewFactory;
@@ -217,6 +222,10 @@ private:
GstElement *m_encodeBin;
#if GST_CHECK_VERSION(1,0,0)
GstVideoInfo m_previewInfo;
#endif
public:
bool m_passImage;
bool m_passPrerollImage;

View File

@@ -34,7 +34,7 @@
#include "qgstreamervideoencode.h"
#include "qgstreamercapturesession.h"
#include "qgstreamermediacontainercontrol.h"
#include <private/qgstutils_p.h>
#include <QtCore/qdebug.h>
#include <math.h>
@@ -147,7 +147,7 @@ GstElement *QGstreamerVideoEncode::createEncoder()
GstElement *capsFilter = gst_element_factory_make("capsfilter", "capsfilter-video");
gst_bin_add(encoderBin, capsFilter);
GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", NULL);
GstElement *colorspace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, NULL);
gst_bin_add(encoderBin, colorspace);
gst_bin_add(encoderBin, encoderElement);
@@ -252,27 +252,22 @@ GstElement *QGstreamerVideoEncode::createEncoder()
}
if (!m_videoSettings.resolution().isEmpty() || m_videoSettings.frameRate() > 0.001) {
GstCaps *caps = gst_caps_new_empty();
QStringList structureTypes;
structureTypes << "video/x-raw-yuv" << "video/x-raw-rgb";
GstCaps *caps = QGstUtils::videoFilterCaps();
foreach(const QString &structureType, structureTypes) {
GstStructure *structure = gst_structure_new(structureType.toLatin1().constData(), NULL);
if (!m_videoSettings.resolution().isEmpty()) {
gst_caps_set_simple(
caps,
"width", G_TYPE_INT, m_videoSettings.resolution().width(),
"height", G_TYPE_INT, m_videoSettings.resolution().height(),
NULL);
}
if (!m_videoSettings.resolution().isEmpty()) {
gst_structure_set(structure, "width", G_TYPE_INT, m_videoSettings.resolution().width(), NULL);
gst_structure_set(structure, "height", G_TYPE_INT, m_videoSettings.resolution().height(), NULL);
}
if (m_videoSettings.frameRate() > 0.001) {
QPair<int,int> rate = rateAsRational();
//qDebug() << "frame rate:" << num << denum;
gst_structure_set(structure, "framerate", GST_TYPE_FRACTION, rate.first, rate.second, NULL);
}
gst_caps_append_structure(caps,structure);
if (m_videoSettings.frameRate() > 0.001) {
QPair<int,int> rate = rateAsRational();
gst_caps_set_simple(
caps,
"framerate", GST_TYPE_FRACTION, rate.first, rate.second,
NULL);
}
//qDebug() << "set video caps filter:" << gst_caps_to_string(caps);

View File

@@ -28,4 +28,3 @@ SOURCES += \
OTHER_FILES += \
mediaplayer.json

View File

@@ -425,7 +425,6 @@ void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *
m_session->loadFromUri(request);
#endif
#if defined(HAVE_GST_APPSRC)
if (!request.url().isEmpty() || userStreamValid) {
#else

View File

@@ -51,7 +51,11 @@
#include <private/qgstreamervideorenderer_p.h>
#if defined(Q_WS_MAEMO_6) && defined(__arm__)
#include "qgstreamergltexturerenderer.h"
#include "private/qgstreamergltexturerenderer.h"
#endif
#if defined(HAVE_MIR) && defined (__arm__)
#include "private/qgstreamermirtexturerenderer_p.h"
#endif
#include "qgstreamerstreamscontrol.h"
@@ -66,6 +70,8 @@ QT_BEGIN_NAMESPACE
QGstreamerPlayerService::QGstreamerPlayerService(QObject *parent):
QMediaService(parent)
, m_audioProbeControl(0)
, m_videoProbeControl(0)
, m_videoOutput(0)
, m_videoRenderer(0)
, m_videoWindow(0)
@@ -82,6 +88,8 @@ QGstreamerPlayerService::QGstreamerPlayerService(QObject *parent):
#if defined(Q_WS_MAEMO_6) && defined(__arm__)
m_videoRenderer = new QGstreamerGLTextureRenderer(this);
#elif defined(HAVE_MIR) && defined (__arm__)
m_videoRenderer = new QGstreamerMirTextureRenderer(this, m_session);
#else
m_videoRenderer = new QGstreamerVideoRenderer(this);
#endif
@@ -115,23 +123,23 @@ QMediaControl *QGstreamerPlayerService::requestControl(const char *name)
if (qstrcmp(name, QMediaAvailabilityControl_iid) == 0)
return m_availabilityControl;
if (qstrcmp(name,QMediaVideoProbeControl_iid) == 0) {
if (m_session) {
QGstreamerVideoProbeControl *probe = new QGstreamerVideoProbeControl(this);
if (qstrcmp(name, QMediaVideoProbeControl_iid) == 0) {
if (!m_videoProbeControl) {
increaseVideoRef();
m_session->addProbe(probe);
return probe;
m_videoProbeControl = new QGstreamerVideoProbeControl(this);
m_session->addProbe(m_videoProbeControl);
}
return 0;
m_videoProbeControl->ref.ref();
return m_videoProbeControl;
}
if (qstrcmp(name,QMediaAudioProbeControl_iid) == 0) {
if (m_session) {
QGstreamerAudioProbeControl *probe = new QGstreamerAudioProbeControl(this);
m_session->addProbe(probe);
return probe;
if (qstrcmp(name, QMediaAudioProbeControl_iid) == 0) {
if (!m_audioProbeControl) {
m_audioProbeControl = new QGstreamerAudioProbeControl(this);
m_session->addProbe(m_audioProbeControl);
}
return 0;
m_audioProbeControl->ref.ref();
return m_audioProbeControl;
}
if (!m_videoOutput) {
@@ -156,28 +164,21 @@ QMediaControl *QGstreamerPlayerService::requestControl(const char *name)
void QGstreamerPlayerService::releaseControl(QMediaControl *control)
{
if (control == m_videoOutput) {
if (!control) {
return;
} else if (control == m_videoOutput) {
m_videoOutput = 0;
m_control->setVideoOutput(0);
decreaseVideoRef();
}
QGstreamerVideoProbeControl* videoProbe = qobject_cast<QGstreamerVideoProbeControl*>(control);
if (videoProbe) {
if (m_session) {
m_session->removeProbe(videoProbe);
decreaseVideoRef();
}
delete videoProbe;
return;
}
QGstreamerAudioProbeControl* audioProbe = qobject_cast<QGstreamerAudioProbeControl*>(control);
if (audioProbe) {
if (m_session)
m_session->removeProbe(audioProbe);
delete audioProbe;
return;
} else if (control == m_videoProbeControl && !m_videoProbeControl->ref.deref()) {
m_session->removeProbe(m_videoProbeControl);
delete m_videoProbeControl;
m_videoProbeControl = 0;
decreaseVideoRef();
} else if (control == m_audioProbeControl && !m_audioProbeControl->ref.deref()) {
m_session->removeProbe(m_audioProbeControl);
delete m_audioProbeControl;
m_audioProbeControl = 0;
}
}

View File

@@ -52,6 +52,8 @@ class QGstreamerStreamsControl;
class QGstreamerVideoRenderer;
class QGstreamerVideoWidgetControl;
class QGStreamerAvailabilityControl;
class QGstreamerAudioProbeControl;
class QGstreamerVideoProbeControl;
class QGstreamerPlayerService : public QMediaService
{
@@ -70,6 +72,9 @@ private:
QGstreamerStreamsControl *m_streamsControl;
QGStreamerAvailabilityControl *m_availabilityControl;
QGstreamerAudioProbeControl *m_audioProbeControl;
QGstreamerVideoProbeControl *m_videoProbeControl;
QMediaControl *m_videoOutput;
QMediaControl *m_videoRenderer;
QMediaControl *m_videoWindow;

View File

@@ -81,89 +81,15 @@ QMultimedia::SupportEstimate QGstreamerPlayerServicePlugin::hasSupport(const QSt
return QGstUtils::hasSupport(mimeType, codecs, m_supportedMimeTypeSet);
}
static bool isDecoderOrDemuxer(GstElementFactory *factory)
{
return gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DEMUXER)
|| gst_element_factory_list_is_type(factory, GST_ELEMENT_FACTORY_TYPE_DECODER);
}
void QGstreamerPlayerServicePlugin::updateSupportedMimeTypes() const
{
//enumerate supported mime types
gst_init(NULL, NULL);
GList *plugins, *orig_plugins;
orig_plugins = plugins = gst_default_registry_get_plugin_list ();
while (plugins) {
GList *features, *orig_features;
GstPlugin *plugin = (GstPlugin *) (plugins->data);
plugins = g_list_next (plugins);
if (plugin->flags & (1<<1)) //GST_PLUGIN_FLAG_BLACKLISTED
continue;
orig_features = features = gst_registry_get_feature_list_by_plugin(gst_registry_get_default (),
plugin->desc.name);
while (features) {
if (!G_UNLIKELY(features->data == NULL)) {
GstPluginFeature *feature = GST_PLUGIN_FEATURE(features->data);
if (GST_IS_ELEMENT_FACTORY (feature)) {
GstElementFactory *factory = GST_ELEMENT_FACTORY(gst_plugin_feature_load(feature));
if (factory
&& factory->numpadtemplates > 0
&& (qstrcmp(factory->details.klass, "Codec/Decoder/Audio") == 0
|| qstrcmp(factory->details.klass, "Codec/Decoder/Video") == 0
|| qstrcmp(factory->details.klass, "Codec/Demux") == 0 )) {
const GList *pads = factory->staticpadtemplates;
while (pads) {
GstStaticPadTemplate *padtemplate = (GstStaticPadTemplate*)(pads->data);
pads = g_list_next (pads);
if (padtemplate->direction != GST_PAD_SINK)
continue;
if (padtemplate->static_caps.string) {
GstCaps *caps = gst_static_caps_get(&padtemplate->static_caps);
if (!gst_caps_is_any (caps) && ! gst_caps_is_empty (caps)) {
for (guint i = 0; i < gst_caps_get_size(caps); i++) {
GstStructure *structure = gst_caps_get_structure(caps, i);
QString nameLowcase = QString(gst_structure_get_name (structure)).toLower();
m_supportedMimeTypeSet.insert(nameLowcase);
if (nameLowcase.contains("mpeg")) {
//Because mpeg version number is only included in the detail
//description, it is necessary to manually extract this information
//in order to match the mime type of mpeg4.
const GValue *value = gst_structure_get_value(structure, "mpegversion");
if (value) {
gchar *str = gst_value_serialize (value);
QString versions(str);
QStringList elements = versions.split(QRegExp("\\D+"), QString::SkipEmptyParts);
foreach (const QString &e, elements)
m_supportedMimeTypeSet.insert(nameLowcase + e);
g_free (str);
}
}
}
}
}
}
gst_object_unref (factory);
}
} else if (GST_IS_TYPE_FIND_FACTORY(feature)) {
QString name(gst_plugin_feature_get_name(feature));
if (name.contains('/')) //filter out any string without '/' which is obviously not a mime type
m_supportedMimeTypeSet.insert(name.toLower());
}
}
features = g_list_next (features);
}
gst_plugin_feature_list_free (orig_features);
}
gst_plugin_list_free (orig_plugins);
#if defined QT_SUPPORTEDMIMETYPES_DEBUG
QStringList list = m_supportedMimeTypeSet.toList();
list.sort();
if (qgetenv("QT_DEBUG_PLUGINS").toInt() > 0) {
foreach (const QString &type, list)
qDebug() << type;
}
#endif
m_supportedMimeTypeSet = QGstUtils::supportedMimeTypes(isDecoderOrDemuxer);
}
QStringList QGstreamerPlayerServicePlugin::supportedMimeTypes() const

View File

@@ -37,7 +37,9 @@
#include <private/qgstreameraudioprobecontrol_p.h>
#include <private/qgstreamervideoprobecontrol_p.h>
#include <private/qgstreamervideorendererinterface_p.h>
#if !GST_CHECK_VERSION(1,0,0)
#include <private/gstvideoconnector_p.h>
#endif
#include <private/qgstutils_p.h>
#include <private/playlistfileparser_p.h>
#include <private/qgstutils_p.h>
@@ -85,6 +87,7 @@ typedef enum {
GST_PLAY_FLAG_BUFFERING = 0x000000100
} GstPlayFlags;
#if !GST_CHECK_VERSION(1,0,0)
#define DEFAULT_RAW_CAPS \
"video/x-raw-yuv; " \
"video/x-raw-rgb; " \
@@ -97,7 +100,9 @@ typedef enum {
"text/x-pango-markup; " \
"video/x-dvd-subpicture; " \
"subpicture/x-pgs"
static GstStaticCaps static_RawCaps = GST_STATIC_CAPS(DEFAULT_RAW_CAPS);
#endif
QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
:QObject(parent),
@@ -105,7 +110,9 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
m_pendingState(QMediaPlayer::StoppedState),
m_busHelper(0),
m_playbin(0),
#if !GST_CHECK_VERSION(1,0,0)
m_usingColorspaceElement(false),
#endif
m_videoSink(0),
m_pendingVideoSink(0),
m_nullVideoSink(0),
@@ -117,8 +124,8 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
#if defined(HAVE_GST_APPSRC)
m_appSrc(0),
#endif
m_videoBufferProbeId(-1),
m_audioBufferProbeId(-1),
m_videoProbe(0),
m_audioProbe(0),
m_volume(100),
m_playbackRate(1.0),
m_muted(false),
@@ -138,8 +145,7 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
Q_ASSERT(result == TRUE);
Q_UNUSED(result);
m_playbin = gst_element_factory_make("playbin2", NULL);
m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, NULL);
if (m_playbin) {
//GST_PLAY_FLAG_NATIVE_VIDEO omits configuration of ffmpegcolorspace and videoscale,
//since those elements are included in the video output bin when necessary.
@@ -147,13 +153,14 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
int flags = GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO |
GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_NATIVE_AUDIO;
#else
int flags = 0;
g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL);
int flags = GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO;
QByteArray envFlags = qgetenv("QT_GSTREAMER_PLAYBIN_FLAGS");
if (!envFlags.isEmpty()) {
flags |= envFlags.toInt();
#if !GST_CHECK_VERSION(1,0,0)
} else {
flags |= GST_PLAY_FLAG_NATIVE_VIDEO;
#endif
}
#endif
g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL);
@@ -185,12 +192,16 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
}
}
#if GST_CHECK_VERSION(1,0,0)
m_videoIdentity = gst_element_factory_make("identity", NULL); // floating ref
#else
m_videoIdentity = GST_ELEMENT(g_object_new(gst_video_connector_get_type(), 0)); // floating ref
g_signal_connect(G_OBJECT(m_videoIdentity), "connection-failed", G_CALLBACK(insertColorSpaceElement), (gpointer)this);
m_colorSpace = gst_element_factory_make(QT_GSTREAMER_COLORCONVERSION_ELEMENT_NAME, "ffmpegcolorspace-vo");
m_colorSpace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-vo");
// might not get a parent, take ownership to avoid leak
qt_gst_object_ref_sink(GST_OBJECT(m_colorSpace));
#endif
m_nullVideoSink = gst_element_factory_make("fakesink", NULL);
g_object_set(G_OBJECT(m_nullVideoSink), "sync", true, NULL);
@@ -206,7 +217,7 @@ QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent)
// add ghostpads
GstPad *pad = gst_element_get_static_pad(m_videoIdentity,"sink");
gst_element_add_pad(GST_ELEMENT(m_videoOutputBin), gst_ghost_pad_new("videosink", pad));
gst_element_add_pad(GST_ELEMENT(m_videoOutputBin), gst_ghost_pad_new("sink", pad));
gst_object_unref(GST_OBJECT(pad));
if (m_playbin != 0) {
@@ -244,7 +255,9 @@ QGstreamerPlayerSession::~QGstreamerPlayerSession()
delete m_busHelper;
gst_object_unref(GST_OBJECT(m_bus));
gst_object_unref(GST_OBJECT(m_playbin));
#if !GST_CHECK_VERSION(1,0,0)
gst_object_unref(GST_OBJECT(m_colorSpace));
#endif
gst_object_unref(GST_OBJECT(m_nullVideoSink));
gst_object_unref(GST_OBJECT(m_videoOutputBin));
}
@@ -339,12 +352,10 @@ qint64 QGstreamerPlayerSession::duration() const
qint64 QGstreamerPlayerSession::position() const
{
GstFormat format = GST_FORMAT_TIME;
gint64 position = 0;
if ( m_playbin && gst_element_query_position(m_playbin, &format, &position))
if (m_playbin && qt_gst_element_query_position(m_playbin, GST_FORMAT_TIME, &position))
m_lastPosition = position / 1000000;
return m_lastPosition;
}
@@ -474,17 +485,26 @@ bool QGstreamerPlayerSession::isAudioAvailable() const
return m_audioAvailable;
}
#if GST_CHECK_VERSION(1,0,0)
static GstPadProbeReturn block_pad_cb(GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
#else
static void block_pad_cb(GstPad *pad, gboolean blocked, gpointer user_data)
#endif
{
Q_UNUSED(pad);
#if GST_CHECK_VERSION(1,0,0)
Q_UNUSED(info);
Q_UNUSED(user_data);
return GST_PAD_PROBE_OK;
#else
#ifdef DEBUG_PLAYBIN
qDebug() << "block_pad_cb, blocked:" << blocked;
#endif
if (blocked && user_data) {
QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession*>(user_data);
QMetaObject::invokeMethod(session, "finishVideoOutputChange", Qt::QueuedConnection);
}
#endif
}
void QGstreamerPlayerSession::updateVideoRenderer()
@@ -529,7 +549,7 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
m_renderer = renderer;
#ifdef DEBUG_VO_BIN_DUMP
_gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/),
"playbin_set");
#endif
@@ -570,12 +590,14 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
gst_element_set_state(m_videoSink, GST_STATE_NULL);
gst_element_set_state(m_playbin, GST_STATE_NULL);
#if !GST_CHECK_VERSION(1,0,0)
if (m_usingColorspaceElement) {
gst_element_unlink(m_colorSpace, m_videoSink);
gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace);
} else {
gst_element_unlink(m_videoIdentity, m_videoSink);
}
#endif
removeVideoBufferProbe();
@@ -585,8 +607,9 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink);
m_usingColorspaceElement = false;
bool linked = gst_element_link(m_videoIdentity, m_videoSink);
#if !GST_CHECK_VERSION(1,0,0)
m_usingColorspaceElement = false;
if (!linked) {
m_usingColorspaceElement = true;
#ifdef DEBUG_PLAYBIN
@@ -595,6 +618,10 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace);
linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL);
}
#endif
if (!linked)
qWarning() << "Linking video output element failed";
if (g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "show-preroll-frame") != 0) {
gboolean value = m_displayPrerolledFrame;
@@ -633,7 +660,11 @@ void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput)
//block pads, async to avoid locking in paused state
GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src");
#if GST_CHECK_VERSION(1,0,0)
this->pad_probe_id = gst_pad_add_probe(srcPad, (GstPadProbeType)(GST_PAD_PROBE_TYPE_BUFFER | GST_PAD_PROBE_TYPE_BLOCKING), block_pad_cb, this, NULL);
#else
gst_pad_set_blocked_async(srcPad, true, &block_pad_cb, this);
#endif
gst_object_unref(GST_OBJECT(srcPad));
//Unpause the sink to avoid waiting until the buffer is processed
@@ -671,16 +702,22 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
}
if (m_pendingVideoSink == m_videoSink) {
qDebug() << "Abort, no change";
//video output was change back to the current one,
//no need to torment the pipeline, just unblock the pad
if (gst_pad_is_blocked(srcPad))
#if GST_CHECK_VERSION(1,0,0)
gst_pad_remove_probe(srcPad, this->pad_probe_id);
#else
gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0);
#endif
m_pendingVideoSink = 0;
gst_object_unref(GST_OBJECT(srcPad));
return;
}
#if !GST_CHECK_VERSION(1,0,0)
if (m_usingColorspaceElement) {
gst_element_set_state(m_colorSpace, GST_STATE_NULL);
gst_element_set_state(m_videoSink, GST_STATE_NULL);
@@ -688,6 +725,9 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
gst_element_unlink(m_colorSpace, m_videoSink);
gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace);
} else {
#else
{
#endif
gst_element_set_state(m_videoSink, GST_STATE_NULL);
gst_element_unlink(m_videoIdentity, m_videoSink);
}
@@ -703,8 +743,9 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
addVideoBufferProbe();
m_usingColorspaceElement = false;
bool linked = gst_element_link(m_videoIdentity, m_videoSink);
#if !GST_CHECK_VERSION(1,0,0)
m_usingColorspaceElement = false;
if (!linked) {
m_usingColorspaceElement = true;
#ifdef DEBUG_PLAYBIN
@@ -713,6 +754,7 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace);
linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL);
}
#endif
if (!linked)
qWarning() << "Linking video output element failed";
@@ -720,6 +762,8 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
#ifdef DEBUG_PLAYBIN
qDebug() << "notify the video connector it has to emit a new segment message...";
#endif
#if !GST_CHECK_VERSION(1,0,0)
//it's necessary to send a new segment event just before
//the first buffer pushed to the new sink
g_signal_emit_by_name(m_videoIdentity,
@@ -727,7 +771,7 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
true //emit connection-failed signal
//to have a chance to insert colorspace element
);
#endif
GstState state = GST_STATE_VOID_PENDING;
@@ -743,8 +787,10 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
break;
}
#if !GST_CHECK_VERSION(1,0,0)
if (m_usingColorspaceElement)
gst_element_set_state(m_colorSpace, state);
#endif
gst_element_set_state(m_videoSink, state);
@@ -760,16 +806,23 @@ void QGstreamerPlayerSession::finishVideoOutputChange()
//don't have to wait here, it will unblock eventually
if (gst_pad_is_blocked(srcPad))
gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0);
#if GST_CHECK_VERSION(1,0,0)
gst_pad_remove_probe(srcPad, this->pad_probe_id);
#else
gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0);
#endif
gst_object_unref(GST_OBJECT(srcPad));
#ifdef DEBUG_VO_BIN_DUMP
_gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES*/),
gst_debug_bin_to_dot_file_with_ts(GST_BIN(m_playbin),
GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_ALL /* | GST_DEBUG_GRAPH_SHOW_MEDIA_TYPE | GST_DEBUG_GRAPH_SHOW_NON_DEFAULT_PARAMS | GST_DEBUG_GRAPH_SHOW_STATES */),
"playbin_finish");
#endif
}
#if !GST_CHECK_VERSION(1,0,0)
void QGstreamerPlayerSession::insertColorSpaceElement(GstElement *element, gpointer data)
{
#ifdef DEBUG_PLAYBIN
@@ -814,6 +867,7 @@ void QGstreamerPlayerSession::insertColorSpaceElement(GstElement *element, gpoin
gst_element_set_state(session->m_colorSpace, state);
}
#endif
bool QGstreamerPlayerSession::isVideoAvailable() const
{
@@ -830,6 +884,7 @@ bool QGstreamerPlayerSession::play()
#ifdef DEBUG_PLAYBIN
qDebug() << Q_FUNC_INFO;
#endif
m_everPlayed = false;
if (m_playbin) {
m_pendingState = QMediaPlayer::PlayingState;
@@ -1161,21 +1216,20 @@ bool QGstreamerPlayerSession::processBusMessage(const QGstreamerMessage &message
case GST_MESSAGE_SEGMENT_DONE:
break;
case GST_MESSAGE_LATENCY:
#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 13)
#if GST_CHECK_VERSION(0,10,13)
case GST_MESSAGE_ASYNC_START:
break;
case GST_MESSAGE_ASYNC_DONE:
{
GstFormat format = GST_FORMAT_TIME;
gint64 position = 0;
if (gst_element_query_position(m_playbin, &format, &position)) {
if (qt_gst_element_query_position(m_playbin, GST_FORMAT_TIME, &position)) {
position /= 1000000;
m_lastPosition = position;
emit positionChanged(position);
}
break;
}
#if GST_VERSION_MICRO >= 23
#if GST_CHECK_VERSION(0,10,23)
case GST_MESSAGE_REQUEST_STATE:
#endif
#endif
@@ -1327,8 +1381,11 @@ void QGstreamerPlayerSession::getStreamsInfo()
default:
break;
}
#if GST_CHECK_VERSION(1,0,0)
if (tags && GST_IS_TAG_LIST(tags)) {
#else
if (tags && gst_is_tag_list(tags)) {
#endif
gchar *languageCode = 0;
if (gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &languageCode))
streamProperties[QMediaMetaData::Language] = QString::fromUtf8(languageCode);
@@ -1365,9 +1422,8 @@ void QGstreamerPlayerSession::updateVideoResolutionTag()
#endif
QSize size;
QSize aspectRatio;
GstPad *pad = gst_element_get_static_pad(m_videoIdentity, "src");
GstCaps *caps = gst_pad_get_negotiated_caps(pad);
GstCaps *caps = qt_gst_pad_get_current_caps(pad);
if (caps) {
const GstStructure *structure = gst_caps_get_structure(caps, 0);
@@ -1407,11 +1463,10 @@ void QGstreamerPlayerSession::updateVideoResolutionTag()
void QGstreamerPlayerSession::updateDuration()
{
GstFormat format = GST_FORMAT_TIME;
gint64 gstDuration = 0;
int duration = -1;
if (m_playbin && gst_element_query_duration(m_playbin, &format, &gstDuration))
if (m_playbin && qt_gst_element_query_duration(m_playbin, GST_FORMAT_TIME, &gstDuration))
duration = gstDuration / 1000000;
if (m_duration != duration) {
@@ -1467,7 +1522,7 @@ void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpo
// The rest
if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0) {
GstStructure *extras = gst_structure_empty_new("extras");
GstStructure *extras = qt_gst_structure_new_empty("extras");
foreach (const QByteArray &rawHeader, self->m_request.rawHeaderList()) {
if (rawHeader == userAgentString) // Filter User-Agent
@@ -1528,7 +1583,8 @@ void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpo
qDebug() << "Current source is a non-live source";
#endif
g_object_set(G_OBJECT(self->m_videoSink), "sync", !self->m_isLiveSource, NULL);
if (self->m_videoSink)
g_object_set(G_OBJECT(self->m_videoSink), "sync", !self->m_isLiveSource, NULL);
gst_object_unref(source);
}
@@ -1623,7 +1679,11 @@ GstAutoplugSelectResult QGstreamerPlayerSession::handleAutoplugSelect(GstBin *bi
const gchar *factoryName = gst_plugin_feature_get_name(GST_PLUGIN_FEATURE(factory));
if (g_str_has_prefix(factoryName, "vaapi")) {
GstPad *sinkPad = gst_element_get_static_pad(session->m_videoSink, "sink");
#if GST_CHECK_VERSION(1,0,0)
GstCaps *sinkCaps = gst_pad_query_caps(sinkPad, NULL);
#else
GstCaps *sinkCaps = gst_pad_get_caps(sinkPad);
#endif
#if (GST_VERSION_MAJOR == 0) && ((GST_VERSION_MINOR < 10) || (GST_VERSION_MICRO < 33))
if (!factory_can_src_any_caps(factory, sinkCaps))
@@ -1652,8 +1712,10 @@ void QGstreamerPlayerSession::handleElementAdded(GstBin *bin, GstElement *elemen
// Disable on-disk buffering.
g_object_set(G_OBJECT(element), "temp-template", NULL, NULL);
} else if (g_str_has_prefix(elementName, "uridecodebin") ||
g_str_has_prefix(elementName, "decodebin2")) {
#if GST_CHECK_VERSION(1,0,0)
g_str_has_prefix(elementName, "decodebin")) {
#else
g_str_has_prefix(elementName, "decodebin2")) {
if (g_str_has_prefix(elementName, "uridecodebin")) {
// Add video/x-surface (VAAPI) to default raw formats
g_object_set(G_OBJECT(element), "caps", gst_static_caps_get(&static_RawCaps), NULL);
@@ -1661,7 +1723,7 @@ void QGstreamerPlayerSession::handleElementAdded(GstBin *bin, GstElement *elemen
// video sink doesn't support it
g_signal_connect(element, "autoplug-select", G_CALLBACK(handleAutoplugSelect), session);
}
#endif
//listen for queue2 element added to uridecodebin/decodebin2 as well.
//Don't touch other bins since they may have unrelated queues
g_signal_connect(element, "element-added",
@@ -1711,68 +1773,30 @@ void QGstreamerPlayerSession::showPrerollFrames(bool enabled)
void QGstreamerPlayerSession::addProbe(QGstreamerVideoProbeControl* probe)
{
QMutexLocker locker(&m_videoProbeMutex);
if (m_videoProbes.contains(probe))
return;
m_videoProbes.append(probe);
Q_ASSERT(!m_videoProbe);
m_videoProbe = probe;
addVideoBufferProbe();
}
void QGstreamerPlayerSession::removeProbe(QGstreamerVideoProbeControl* probe)
{
QMutexLocker locker(&m_videoProbeMutex);
m_videoProbes.removeOne(probe);
// Do not emit flush signal in this case.
// Assume user releases any outstanding references to video frames.
}
gboolean QGstreamerPlayerSession::padVideoBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data)
{
Q_UNUSED(pad);
QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession*>(user_data);
QMutexLocker locker(&session->m_videoProbeMutex);
if (session->m_videoProbes.isEmpty())
return TRUE;
foreach (QGstreamerVideoProbeControl* probe, session->m_videoProbes)
probe->bufferProbed(buffer);
return TRUE;
Q_ASSERT(m_videoProbe == probe);
removeVideoBufferProbe();
m_videoProbe = 0;
}
void QGstreamerPlayerSession::addProbe(QGstreamerAudioProbeControl* probe)
{
QMutexLocker locker(&m_audioProbeMutex);
if (m_audioProbes.contains(probe))
return;
m_audioProbes.append(probe);
Q_ASSERT(!m_audioProbe);
m_audioProbe = probe;
addAudioBufferProbe();
}
void QGstreamerPlayerSession::removeProbe(QGstreamerAudioProbeControl* probe)
{
QMutexLocker locker(&m_audioProbeMutex);
m_audioProbes.removeOne(probe);
}
gboolean QGstreamerPlayerSession::padAudioBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data)
{
Q_UNUSED(pad);
QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession*>(user_data);
QMutexLocker locker(&session->m_audioProbeMutex);
if (session->m_audioProbes.isEmpty())
return TRUE;
foreach (QGstreamerAudioProbeControl* probe, session->m_audioProbes)
probe->bufferProbed(buffer);
return TRUE;
Q_ASSERT(m_audioProbe == probe);
removeAudioBufferProbe();
m_audioProbe = 0;
}
// This function is similar to stop(),
@@ -1797,80 +1821,62 @@ void QGstreamerPlayerSession::endOfMediaReset()
void QGstreamerPlayerSession::removeVideoBufferProbe()
{
if (m_videoBufferProbeId == -1)
if (!m_videoProbe)
return;
if (!m_videoSink) {
m_videoBufferProbeId = -1;
return;
}
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
if (pad) {
gst_pad_remove_buffer_probe(pad, m_videoBufferProbeId);
m_videoProbe->removeProbeFromPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
m_videoBufferProbeId = -1;
}
void QGstreamerPlayerSession::addVideoBufferProbe()
{
Q_ASSERT(m_videoBufferProbeId == -1);
if (!m_videoSink)
if (!m_videoProbe)
return;
GstPad *pad = gst_element_get_static_pad(m_videoSink, "sink");
if (pad) {
m_videoBufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padVideoBufferProbe), this);
m_videoProbe->addProbeToPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
}
void QGstreamerPlayerSession::removeAudioBufferProbe()
{
if (m_audioBufferProbeId == -1)
if (!m_audioProbe)
return;
if (!m_audioSink) {
m_audioBufferProbeId = -1;
return;
}
GstPad *pad = gst_element_get_static_pad(m_audioSink, "sink");
if (pad) {
gst_pad_remove_buffer_probe(pad, m_audioBufferProbeId);
m_audioProbe->removeProbeFromPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
m_audioBufferProbeId = -1;
}
void QGstreamerPlayerSession::addAudioBufferProbe()
{
Q_ASSERT(m_audioBufferProbeId == -1);
if (!m_audioSink)
if (!m_audioProbe)
return;
GstPad *pad = gst_element_get_static_pad(m_audioSink, "sink");
if (pad) {
m_audioBufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padAudioBufferProbe), this);
m_audioProbe->addProbeToPad(pad);
gst_object_unref(GST_OBJECT(pad));
}
}
void QGstreamerPlayerSession::flushVideoProbes()
{
QMutexLocker locker(&m_videoProbeMutex);
foreach (QGstreamerVideoProbeControl* probe, m_videoProbes)
probe->startFlushing();
if (m_videoProbe)
m_videoProbe->startFlushing();
}
void QGstreamerPlayerSession::resumeVideoProbes()
{
QMutexLocker locker(&m_videoProbeMutex);
foreach (QGstreamerVideoProbeControl* probe, m_videoProbes)
probe->stopFlushing();
if (m_videoProbe)
m_videoProbe->stopFlushing();
}
void QGstreamerPlayerSession::playlistTypeFindFunction(GstTypeFind *find, gpointer userData)
@@ -1878,7 +1884,11 @@ void QGstreamerPlayerSession::playlistTypeFindFunction(GstTypeFind *find, gpoint
QGstreamerPlayerSession* session = (QGstreamerPlayerSession*)userData;
const gchar *uri = 0;
#if GST_CHECK_VERSION(1,0,0)
g_object_get(G_OBJECT(session->m_playbin), "current-uri", &uri, NULL);
#else
g_object_get(G_OBJECT(session->m_playbin), "uri", &uri, NULL);
#endif
guint64 length = gst_type_find_get_length(find);
if (!length)
@@ -1887,7 +1897,7 @@ void QGstreamerPlayerSession::playlistTypeFindFunction(GstTypeFind *find, gpoint
length = qMin(length, guint64(1024));
while (length > 0) {
guint8 *data = gst_type_find_peek(find, 0, length);
const guint8 *data = gst_type_find_peek(find, 0, length);
if (data) {
session->m_isPlaylist = (QPlaylistFileParser::findPlaylistType(QString::fromUtf8(uri), 0, data, length) != QPlaylistFileParser::UNKNOWN);
return;

View File

@@ -119,11 +119,9 @@ public:
void addProbe(QGstreamerVideoProbeControl* probe);
void removeProbe(QGstreamerVideoProbeControl* probe);
static gboolean padVideoBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data);
void addProbe(QGstreamerAudioProbeControl* probe);
void removeProbe(QGstreamerAudioProbeControl* probe);
static gboolean padAudioBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data);
void endOfMediaReset();
@@ -172,7 +170,9 @@ private:
static void playbinNotifySource(GObject *o, GParamSpec *p, gpointer d);
static void handleVolumeChange(GObject *o, GParamSpec *p, gpointer d);
static void handleMutedChange(GObject *o, GParamSpec *p, gpointer d);
#if !GST_CHECK_VERSION(1,0,0)
static void insertColorSpaceElement(GstElement *element, gpointer data);
#endif
static void handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session);
static void handleStreamsChange(GstBin *bin, gpointer user_data);
static GstAutoplugSelectResult handleAutoplugSelect(GstBin *bin, GstPad *pad, GstCaps *caps, GstElementFactory *factory, QGstreamerPlayerSession *session);
@@ -194,11 +194,14 @@ private:
QGstreamerBusHelper* m_busHelper;
GstElement* m_playbin;
GstElement* m_videoSink;
GstElement* m_videoOutputBin;
GstElement* m_videoIdentity;
#if !GST_CHECK_VERSION(1,0,0)
GstElement* m_colorSpace;
bool m_usingColorspaceElement;
GstElement* m_videoSink;
#endif
GstElement* m_pendingVideoSink;
GstElement* m_nullVideoSink;
@@ -218,13 +221,8 @@ private:
QList<QMediaStreamsControl::StreamType> m_streamTypes;
QMap<QMediaStreamsControl::StreamType, int> m_playbin2StreamOffset;
QList<QGstreamerVideoProbeControl*> m_videoProbes;
QMutex m_videoProbeMutex;
int m_videoBufferProbeId;
QList<QGstreamerAudioProbeControl*> m_audioProbes;
QMutex m_audioProbeMutex;
int m_audioBufferProbeId;
QGstreamerVideoProbeControl *m_videoProbe;
QGstreamerAudioProbeControl *m_audioProbe;
int m_volume;
qreal m_playbackRate;
@@ -252,6 +250,7 @@ private:
bool m_isLiveSource;
bool m_isPlaylist;
gulong pad_probe_id;
};
QT_END_NAMESPACE