diff options
author | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
---|---|---|
committer | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
commit | 2a34e88c1e1ced28e75c487cd13402e1c9cf9fa3 (patch) | |
tree | e6c1b770c5c47212792a1f9344fa034ea3e54c44 /src/plugins/gstreamer |
Initial copy of QtMultimediaKit.
Comes from original repo, with SHA1:
2c82d5611655e5967f5c5095af50c0991c4378b2
Diffstat (limited to 'src/plugins/gstreamer')
115 files changed, 21812 insertions, 0 deletions
diff --git a/src/plugins/gstreamer/camerabin/camerabin.pri b/src/plugins/gstreamer/camerabin/camerabin.pri new file mode 100644 index 000000000..5c266e784 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabin.pri @@ -0,0 +1,50 @@ +INCLUDEPATH += $$PWD \ + $${SOURCE_DIR}/src/multimedia + +INCLUDEPATH += camerabin + +DEFINES += QMEDIA_GSTREAMER_CAMERABIN + +LIBS += -lgstphotography-0.10 + +DEFINES += GST_USE_UNSTABLE_API #prevents warnings because of unstable photography API + +HEADERS += \ + $$PWD/camerabinservice.h \ + $$PWD/camerabinsession.h \ + $$PWD/camerabincontrol.h \ + $$PWD/camerabinaudioencoder.h \ + $$PWD/camerabinfocus.h \ + $$PWD/camerabinimageencoder.h \ + $$PWD/camerabinlocks.h \ + $$PWD/camerabinrecorder.h \ + $$PWD/camerabincontainer.h \ + $$PWD/camerabinexposure.h \ + $$PWD/camerabinflash.h \ + $$PWD/camerabinimagecapture.h \ + $$PWD/camerabinimageprocessing.h \ + $$PWD/camerabinmetadata.h \ + $$PWD/camerabinvideoencoder.h \ + $$PWD/camerabinresourcepolicy.h \ + $$PWD/camerabincapturedestination.h \ + $$PWD/camerabincapturebufferformat.h + +SOURCES += \ + $$PWD/camerabinservice.cpp \ + $$PWD/camerabinsession.cpp \ + $$PWD/camerabincontrol.cpp \ + $$PWD/camerabinaudioencoder.cpp \ + $$PWD/camerabincontainer.cpp \ + $$PWD/camerabinexposure.cpp \ + $$PWD/camerabinflash.cpp \ + $$PWD/camerabinfocus.cpp \ + $$PWD/camerabinimagecapture.cpp \ + $$PWD/camerabinimageencoder.cpp \ + $$PWD/camerabinimageprocessing.cpp \ + $$PWD/camerabinlocks.cpp \ + $$PWD/camerabinmetadata.cpp \ + $$PWD/camerabinrecorder.cpp \ + $$PWD/camerabinvideoencoder.cpp \ + $$PWD/camerabinresourcepolicy.cpp \ + $$PWD/camerabincapturedestination.cpp \ + $$PWD/camerabincapturebufferformat.cpp diff --git a/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp new file mode 100644 index 000000000..d028fb3e7 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.cpp @@ -0,0 +1,293 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinaudioencoder.h" +#include "camerabincontainer.h" + +#include <QtCore/qdebug.h> + +CameraBinAudioEncoder::CameraBinAudioEncoder(QObject *parent) + :QAudioEncoderControl(parent) +{ + QList<QByteArray> codecCandidates; + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + codecCandidates << "audio/AAC" << "audio/PCM" << "audio/AMR" << "audio/AMR-WB" << "audio/speex" + << "audio/ADPCM" << "audio/iLBC" << "audio/vorbis" << "audio/mpeg" << "audio/FLAC"; + + m_elementNames["audio/AAC"] = "nokiaaacenc"; + m_elementNames["audio/speex"] = "speexenc"; + m_elementNames["audio/PCM"] = "audioresample"; + m_elementNames["audio/AMR"] = "nokiaamrnbenc"; + m_elementNames["audio/AMR-WB"] = "nokiaamrwbenc"; + m_elementNames["audio/ADPCM"] = "nokiaadpcmenc"; + m_elementNames["audio/iLBC"] = "nokiailbcenc"; + m_elementNames["audio/vorbis"] = "vorbisenc"; + m_elementNames["audio/FLAC"] = "flacenc"; + m_elementNames["audio/mpeg"] = "ffenc_mp2"; +#else + codecCandidates << "audio/mpeg" << "audio/vorbis" << "audio/speex" << "audio/GSM" + << "audio/PCM" << "audio/AMR" << "audio/AMR-WB"; + + m_elementNames["audio/mpeg"] = "lamemp3enc"; + m_elementNames["audio/vorbis"] = "vorbisenc"; + m_elementNames["audio/speex"] = "speexenc"; + m_elementNames["audio/GSM"] = "gsmenc"; + m_elementNames["audio/PCM"] = "audioresample"; + m_elementNames["audio/AMR"] = "amrnbenc"; + m_elementNames["audio/AMR-WB"] = "amrwbenc"; + + m_codecOptions["audio/vorbis"] = QStringList() << "min-bitrate" << "max-bitrate"; + m_codecOptions["audio/mpeg"] = QStringList() << "mode"; + m_codecOptions["audio/speex"] = QStringList() << "mode" << "vbr" << "vad" << "dtx"; + m_codecOptions["audio/GSM"] = QStringList(); + m_codecOptions["audio/PCM"] = QStringList(); + m_codecOptions["audio/AMR"] = QStringList(); + m_codecOptions["audio/AMR-WB"] = QStringList(); +#endif + + foreach( const QByteArray& codecName, codecCandidates ) { + QByteArray elementName = m_elementNames[codecName]; + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + + if (factory) { + m_codecs.append(codecName); + const gchar *descr = gst_element_factory_get_description(factory); + + if (codecName == QByteArray("audio/PCM")) + m_codecDescriptions.insert(codecName, tr("Raw PCM audio")); + else + m_codecDescriptions.insert(codecName, QString::fromUtf8(descr)); + + m_streamTypes.insert(codecName, + CameraBinContainer::supportedStreamTypes(factory, GST_PAD_SRC)); + + gst_object_unref(GST_OBJECT(factory)); + } + } +} + +CameraBinAudioEncoder::~CameraBinAudioEncoder() +{ +} + +QStringList CameraBinAudioEncoder::supportedAudioCodecs() const +{ + return m_codecs; +} + +QString CameraBinAudioEncoder::codecDescription(const QString &codecName) const +{ + return m_codecDescriptions.value(codecName); +} + +QStringList CameraBinAudioEncoder::supportedEncodingOptions(const QString &codec) const +{ + return m_codecOptions.value(codec); +} + +QVariant CameraBinAudioEncoder::encodingOption( + const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void CameraBinAudioEncoder::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QList<int> CameraBinAudioEncoder::supportedSampleRates(const QAudioEncoderSettings &, bool *) const +{ + //TODO check element caps to find actual values + + return QList<int>(); +} + +QAudioEncoderSettings CameraBinAudioEncoder::audioSettings() const +{ + return m_audioSettings; +} + +void CameraBinAudioEncoder::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_userSettings = settings; + m_audioSettings = settings; + emit settingsChanged(); +} + +void CameraBinAudioEncoder::setActualAudioSettings(const QAudioEncoderSettings &settings) +{ + m_audioSettings = settings; +} + +void CameraBinAudioEncoder::resetActualSettings() +{ + m_audioSettings = m_userSettings; +} + +GstElement *CameraBinAudioEncoder::createEncoder() +{ + QString codec = m_audioSettings.codec(); + QByteArray encoderElementName = m_elementNames.value(codec); + GstElement *encoderElement = gst_element_factory_make(encoderElementName.constData(), NULL); + if (!encoderElement) + return 0; + + GstBin * encoderBin = GST_BIN(gst_bin_new("audio-encoder-bin")); + GstElement *capsFilter = gst_element_factory_make("capsfilter", NULL); + + gst_bin_add(encoderBin, capsFilter); + gst_bin_add(encoderBin, encoderElement); + gst_element_link(capsFilter, encoderElement); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(capsFilter, "sink"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(encoderElement, "src"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + 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); + + if (m_audioSettings.sampleRate() > 0) + gst_structure_set(structure, "rate", G_TYPE_INT, m_audioSettings.sampleRate(), NULL ); + + if (m_audioSettings.channelCount() > 0) + gst_structure_set(structure, "channels", G_TYPE_INT, m_audioSettings.channelCount(), NULL ); + + gst_caps_append_structure(caps,structure); + + g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL); + } + + if (encoderElement) { + if (m_audioSettings.encodingMode() == QtMultimediaKit::ConstantQualityEncoding) { + QtMultimediaKit::EncodingQuality qualityValue = m_audioSettings.quality(); + + if (encoderElementName == "lamemp3enc") { + g_object_set(G_OBJECT(encoderElement), "target", 0, NULL); //constant quality mode + qreal quality[] = { + 10.0, //VeryLow + 6.0, //Low + 4.0, //Normal + 2.0, //High + 0.0 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", quality[qualityValue], NULL); + } else if (encoderElementName == "ffenc_mp2") { + int quality[] = { + 8000, //VeryLow + 64000, //Low + 128000, //Normal + 192000, //High + 320000 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "bitrate", quality[qualityValue], NULL); + } else if (codec == QLatin1String("audio/speex")) { + //0-10 range with default 8 + double qualityTable[] = { + 2, //VeryLow + 5, //Low + 8, //Normal + 9, //High + 10 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", qualityTable[qualityValue], NULL); + } else if (codec.startsWith("audio/AMR")) { + int band[] = { + 0, //VeryLow + 2, //Low + 4, //Normal + 6, //High + 7 //VeryHigh + }; + + g_object_set(G_OBJECT(encoderElement), "band-mode", band[qualityValue], NULL); + } + } else { + int bitrate = m_audioSettings.bitRate(); + if (bitrate > 0) { + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString, QVariant> options = m_options.value(codec); + QMapIterator<QString,QVariant> it(options); + while (it.hasNext()) { + it.next(); + QString option = it.key(); + QVariant value = it.value(); + + switch (value.type()) { + case QVariant::Int: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toInt(), NULL); + break; + case QVariant::Bool: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toBool(), NULL); + break; + case QVariant::Double: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toDouble(), NULL); + break; + case QVariant::String: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + } + } + + return GST_ELEMENT(encoderBin); + +} + + +QSet<QString> CameraBinAudioEncoder::supportedStreamTypes(const QString &codecName) const +{ + return m_streamTypes.value(codecName); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h new file mode 100644 index 000000000..1790fc37d --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinaudioencoder.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINAUDIOENCODE_H +#define CAMERABINAUDIOENCODE_H + +#include <qaudioencodercontrol.h> +class CameraBinSession; + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +#include <qaudioformat.h> + +QT_USE_NAMESPACE + +class CameraBinAudioEncoder : public QAudioEncoderControl +{ + Q_OBJECT +public: + CameraBinAudioEncoder(QObject *parent); + virtual ~CameraBinAudioEncoder(); + + QStringList supportedAudioCodecs() const; + QString codecDescription(const QString &codecName) const; + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings = QAudioEncoderSettings(), + bool *isContinuous = 0) const; + QList<int> supportedChannelCounts(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + QList<int> supportedSampleSizes(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + + QAudioEncoderSettings audioSettings() const; + void setAudioSettings(const QAudioEncoderSettings&); + + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + + void setActualAudioSettings(const QAudioEncoderSettings&); + void resetActualSettings(); + +Q_SIGNALS: + void settingsChanged(); + +private: + QStringList m_codecs; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QStringList> m_codecOptions; + + QMap<QString, QMap<QString, QVariant> > m_options; + + QMap<QString, QSet<QString> > m_streamTypes; + + QAudioEncoderSettings m_audioSettings; + QAudioEncoderSettings m_userSettings; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.cpp b/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.cpp new file mode 100644 index 000000000..b99024f22 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabincapturebufferformat.h" +#include "camerabinsession.h" + +CameraBinCaptureBufferFormat::CameraBinCaptureBufferFormat(CameraBinSession *session) + :QCameraCaptureBufferFormatControl(session) + , m_session(session) + , m_format(QVideoFrame::Format_Jpeg) +{ +} + +CameraBinCaptureBufferFormat::~CameraBinCaptureBufferFormat() +{ +} + +QList<QVideoFrame::PixelFormat> CameraBinCaptureBufferFormat::supportedBufferFormats() const +{ + //the exact YUV format is unknown with camerabin until the first capture is requested + return QList<QVideoFrame::PixelFormat>() + << QVideoFrame::Format_Jpeg +#ifdef Q_WS_MAEMO_6 + << QVideoFrame::Format_UYVY +#endif + ; +} + +QVideoFrame::PixelFormat CameraBinCaptureBufferFormat::bufferFormat() const +{ + return m_format; +} + +void CameraBinCaptureBufferFormat::setBufferFormat(QVideoFrame::PixelFormat format) +{ + if (m_format != format) { + m_format = format; + emit bufferFormatChanged(format); + } +} diff --git a/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.h b/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.h new file mode 100644 index 000000000..579ed239e --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincapturebufferformat.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINCAPTUREBUFFERFORMAT_H +#define CAMERABINCAPTUREBUFFERFORMAT_H + +#include <qcamera.h> +#include <qcameracapturebufferformatcontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +class CameraBinSession; + +QT_USE_NAMESPACE + +class Q_MULTIMEDIA_EXPORT CameraBinCaptureBufferFormat : public QCameraCaptureBufferFormatControl +{ + Q_OBJECT +public: + CameraBinCaptureBufferFormat(CameraBinSession *session); + virtual ~CameraBinCaptureBufferFormat(); + + QList<QVideoFrame::PixelFormat> supportedBufferFormats() const; + + QVideoFrame::PixelFormat bufferFormat() const; + void setBufferFormat(QVideoFrame::PixelFormat format); + +private: + CameraBinSession *m_session; + QVideoFrame::PixelFormat m_format; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabin/camerabincapturedestination.cpp b/src/plugins/gstreamer/camerabin/camerabincapturedestination.cpp new file mode 100644 index 000000000..2a83637b4 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincapturedestination.cpp @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabincapturedestination.h" +#include "camerabinsession.h" + +CameraBinCaptureDestination::CameraBinCaptureDestination(CameraBinSession *session) + :QCameraCaptureDestinationControl(session) + , m_session(session) + , m_destination(QCameraImageCapture::CaptureToFile) +{ +} + +CameraBinCaptureDestination::~CameraBinCaptureDestination() +{ +} + + +bool CameraBinCaptureDestination::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const +{ + //capture to buffer, file and both are supported. + return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer); +} + +QCameraImageCapture::CaptureDestinations CameraBinCaptureDestination::captureDestination() const +{ + return m_destination; +} + +void CameraBinCaptureDestination::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + if (m_destination != destination) { + m_destination = destination; + emit captureDestinationChanged(m_destination); + } +} diff --git a/src/plugins/gstreamer/camerabin/camerabincapturedestination.h b/src/plugins/gstreamer/camerabin/camerabincapturedestination.h new file mode 100644 index 000000000..92c02f1b0 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincapturedestination.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINCAPTUREDESTINATION_H +#define CAMERABINCAPTUREDESTINATION_H + +#include <qcameraimagecapture.h> +#include <qcameracapturedestinationcontrol.h> + + +class CameraBinSession; + +QT_USE_NAMESPACE + +class Q_MULTIMEDIA_EXPORT CameraBinCaptureDestination : public QCameraCaptureDestinationControl +{ + Q_OBJECT +public: + CameraBinCaptureDestination(CameraBinSession *session); + virtual ~CameraBinCaptureDestination(); + + bool isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const; + QCameraImageCapture::CaptureDestinations captureDestination() const; + void setCaptureDestination(QCameraImageCapture::CaptureDestinations destination); + +private: + CameraBinSession *m_session; + QCameraImageCapture::CaptureDestinations m_destination; +}; + +#endif // CAMERABINFLASHCONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabincontainer.cpp b/src/plugins/gstreamer/camerabin/camerabincontainer.cpp new file mode 100644 index 000000000..97349204d --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincontainer.cpp @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabincontainer.h" + + +#include <QtCore/qdebug.h> + +CameraBinContainer::CameraBinContainer(QObject *parent) + :QMediaContainerControl(parent) +{ + QList<QByteArray> formatCandidates; + formatCandidates << "mp4" << "ogg" << "wav" << "amr" << "mkv" + << "avi" << "3gp" << "3gp2" << "webm" << "mjpeg" << "asf" << "mov"; + + QMap<QString,QByteArray> elementNames; + + elementNames.insertMulti("mp4", "ffmux_mp4"); + elementNames.insertMulti("mp4", "hantromp4mux"); + elementNames.insertMulti("mp4", "mp4mux"); + elementNames.insert("ogg", "oggmux"); + elementNames["wav"] = "wavenc"; + elementNames["amr"] = "ffmux_amr"; + elementNames["mkv"] = "matroskamux"; + elementNames["avi"] = "avimux"; + elementNames["3gp"] = "ffmux_3gp"; + elementNames["3gp2"] = "ffmux_3g2"; + elementNames["webm"] = "webmmux"; + elementNames["mjpeg"] = "ffmux_mjpeg"; + elementNames["asf"] = "ffmux_asf"; + elementNames["mov"] = "qtmux"; + + QSet<QString> allTypes; + + foreach(const QByteArray &formatName, formatCandidates) { + foreach(const QByteArray &elementName, elementNames.values(formatName)) { + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + if (factory) { + m_supportedContainers.append(formatName); + const gchar *descr = gst_element_factory_get_description(factory); + m_containerDescriptions.insert(formatName, QString::fromUtf8(descr)); + + + if (formatName == QByteArray("raw")) { + m_streamTypes.insert(formatName, allTypes); + } else { + QSet<QString> types = supportedStreamTypes(factory, GST_PAD_SINK); + m_streamTypes.insert(formatName, types); + allTypes.unite(types); + } + + gst_object_unref(GST_OBJECT(factory)); + + m_elementNames.insert(formatName, elementName); + break; + } + } + } +} + +QSet<QString> CameraBinContainer::supportedStreamTypes(GstElementFactory *factory, GstPadDirection direction) +{ + QSet<QString> types; + const GList *pads = gst_element_factory_get_static_pad_templates(factory); + for (const GList *pad = pads; pad; pad = g_list_next(pad)) { + GstStaticPadTemplate *templ = (GstStaticPadTemplate*)pad->data; + if (templ->direction == direction) { + GstCaps *caps = gst_static_caps_get(&templ->static_caps); + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + types.insert( QString::fromUtf8(gst_structure_get_name(structure)) ); + } + gst_caps_unref(caps); + } + } + + return types; +} + + +QSet<QString> CameraBinContainer::supportedStreamTypes(const QString &container) const +{ + return m_streamTypes.value(container); +} diff --git a/src/plugins/gstreamer/camerabin/camerabincontainer.h b/src/plugins/gstreamer/camerabin/camerabincontainer.h new file mode 100644 index 000000000..3eac48342 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincontainer.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABINMEDIACONTAINERCONTROL_H +#define CAMERABINMEDIACONTAINERCONTROL_H + +#include <qmediacontainercontrol.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +QT_USE_NAMESPACE + +class CameraBinContainer : public QMediaContainerControl +{ +Q_OBJECT +public: + CameraBinContainer(QObject *parent); + virtual ~CameraBinContainer() {} + + virtual QStringList supportedContainers() const { return m_supportedContainers; } + virtual QString containerMimeType() const { return m_format; } + virtual void setContainerMimeType(const QString &formatMimeType) + { + m_format = formatMimeType; + + if (m_userFormat != formatMimeType) { + m_userFormat = formatMimeType; + emit settingsChanged(); + } + } + + void setActualContainer(const QString &formatMimeType) + { + m_format = formatMimeType; + } + + void resetActualContainer() + { + m_format = m_userFormat; + } + + virtual QString containerDescription(const QString &formatMimeType) const { return m_containerDescriptions.value(formatMimeType); } + + QByteArray formatElementName() const { return m_elementNames.value(containerMimeType()); } + + QSet<QString> supportedStreamTypes(const QString &container) const; + + static QSet<QString> supportedStreamTypes(GstElementFactory *factory, GstPadDirection direction); + +Q_SIGNALS: + void settingsChanged(); + +private: + QString m_format; // backend selected format, using m_userFormat + QString m_userFormat; + QStringList m_supportedContainers; + QMap<QString,QByteArray> m_elementNames; + QMap<QString, QString> m_containerDescriptions; + QMap<QString, QSet<QString> > m_streamTypes; +}; + +#endif // CAMERABINMEDIACONTAINERCONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabincontrol.cpp b/src/plugins/gstreamer/camerabin/camerabincontrol.cpp new file mode 100644 index 000000000..2c30fd656 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincontrol.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabincontrol.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" +#include "camerabinresourcepolicy.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> +#include <QtCore/qmetaobject.h> + +#include <linux/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <linux/videodev2.h> + +//#define CAMEABIN_DEBUG 1 +#define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + +CameraBinControl::CameraBinControl(CameraBinSession *session) + :QCameraControl(session), + m_session(session), + m_state(QCamera::UnloadedState), + m_status(QCamera::UnloadedStatus), + m_reloadPending(false) +{ + connect(m_session, SIGNAL(stateChanged(QCamera::State)), + this, SLOT(updateStatus())); + + connect(m_session->audioEncodeControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session->videoEncodeControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session->mediaContainerControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session->imageEncodeControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(viewfinderChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(readyChanged(bool)), + SLOT(reloadLater())); + connect(m_session, SIGNAL(error(int,QString)), + SLOT(handleCameraError(int,QString))); + + m_resourcePolicy = new CamerabinResourcePolicy(this); + connect(m_resourcePolicy, SIGNAL(resourcesGranted()), + SLOT(handleResourcesGranted())); + connect(m_resourcePolicy, SIGNAL(resourcesDenied()), + SLOT(handleResourcesLost())); + connect(m_resourcePolicy, SIGNAL(resourcesLost()), + SLOT(handleResourcesLost())); + + connect(m_session, SIGNAL(busyChanged(bool)), + SLOT(handleBusyChanged(bool))); +} + +CameraBinControl::~CameraBinControl() +{ +} + +QCamera::CaptureMode CameraBinControl::captureMode() const +{ + return m_session->captureMode(); +} + +void CameraBinControl::setCaptureMode(QCamera::CaptureMode mode) +{ + if (m_session->captureMode() != mode) { + m_session->setCaptureMode(mode); + reloadLater(); + + if (m_state == QCamera::ActiveState) { + m_resourcePolicy->setResourceSet( + captureMode() == QCamera::CaptureStillImage ? + CamerabinResourcePolicy::ImageCaptureResources : + CamerabinResourcePolicy::VideoCaptureResources); + } + emit captureModeChanged(mode); + } +} + +bool CameraBinControl::isCaptureModeSupported(QCamera::CaptureMode mode) const +{ +#ifdef Q_WS_MAEMO_5 + //Front camera on N900 supports only video capture + if (m_session->cameraRole() == CameraBinSession::FrontCamera) + return mode == QCamera::CaptureVideo; +#endif + + return mode == QCamera::CaptureStillImage || mode == QCamera::CaptureVideo; +} + +void CameraBinControl::setState(QCamera::State state) +{ +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", state); +#endif + if (m_state != state) { + m_state = state; + + //special case for stopping the camera while it's busy, + //it should be delayed until the camera is idle + if (state == QCamera::LoadedState && + m_session->state() == QCamera::ActiveState && + m_session->isBusy()) { +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Camera is busy, QCamera::stop() is delayed"; +#endif + emit stateChanged(m_state); + return; + } + + CamerabinResourcePolicy::ResourceSet resourceSet; + switch (state) { + case QCamera::UnloadedState: + resourceSet = CamerabinResourcePolicy::NoResources; + break; + case QCamera::LoadedState: + resourceSet = CamerabinResourcePolicy::LoadedResources; + break; + case QCamera::ActiveState: + resourceSet = captureMode() == QCamera::CaptureStillImage ? + CamerabinResourcePolicy::ImageCaptureResources : + CamerabinResourcePolicy::VideoCaptureResources; + break; + } + + m_resourcePolicy->setResourceSet(resourceSet); + + if (m_resourcePolicy->isResourcesGranted()) { + //postpone changing to Active if the session is nor ready yet + if (state == QCamera::ActiveState) { + if (m_session->isReady()) { + m_session->setState(state); + } else { +#ifdef CAMEABIN_DEBUG + qDebug() << "Camera session is not ready yet, postpone activating"; +#endif + } + } else + m_session->setState(state); + } + + emit stateChanged(m_state); + } +} + +QCamera::State CameraBinControl::state() const +{ + return m_state; +} + +void CameraBinControl::updateStatus() +{ + QCamera::State sessionState = m_session->state(); + QCamera::Status oldStatus = m_status; + + switch (m_state) { + case QCamera::UnloadedState: + m_status = QCamera::UnloadedStatus; + break; + case QCamera::LoadedState: + switch (sessionState) { + case QCamera::UnloadedState: + m_status = QCamera::LoadingStatus; + break; + case QCamera::LoadedState: + m_status = QCamera::LoadedStatus; + break; + case QCamera::ActiveState: + m_status = QCamera::ActiveStatus; + break; + } + break; + case QCamera::ActiveState: + switch (sessionState) { + case QCamera::UnloadedState: + m_status = QCamera::LoadingStatus; + break; + case QCamera::LoadedState: + m_status = QCamera::StartingStatus; + break; + case QCamera::ActiveState: + m_status = QCamera::ActiveStatus; + break; + } + } + + if (m_status != oldStatus) { +#ifdef CAMEABIN_DEBUG + qDebug() << "Camera status changed" << ENUM_NAME(QCamera, "Status", m_status); +#endif + emit statusChanged(m_status); + } +} + +void CameraBinControl::reloadLater() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << "CameraBinControl: reload pipeline requested" << ENUM_NAME(QCamera, "State", m_state); +#endif + if (!m_reloadPending && m_state == QCamera::ActiveState) { + m_reloadPending = true; + + if (!m_session->isBusy()) { + m_session->setState(QCamera::LoadedState); + QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); + } + } +} + +void CameraBinControl::handleResourcesLost() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", m_state); +#endif + m_session->setState(QCamera::UnloadedState); +} + +void CameraBinControl::handleResourcesGranted() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", m_state); +#endif + + //camera will be started soon by delayedReload() + if (m_reloadPending && m_state == QCamera::ActiveState) + return; + + if (m_state == QCamera::ActiveState && m_session->isReady()) + m_session->setState(QCamera::ActiveState); + else if (m_state == QCamera::LoadedState) + m_session->setState(QCamera::LoadedState); +} + +void CameraBinControl::handleBusyChanged(bool busy) +{ + if (!busy && m_session->state() == QCamera::ActiveState) { + if (m_state == QCamera::LoadedState) { + //handle delayed stop() because of busy camera + m_resourcePolicy->setResourceSet(CamerabinResourcePolicy::LoadedResources); + m_session->setState(QCamera::LoadedState); + } else if (m_state == QCamera::ActiveState && m_reloadPending) { + //handle delayed reload because of busy camera + m_session->setState(QCamera::LoadedState); + QMetaObject::invokeMethod(this, "delayedReload", Qt::QueuedConnection); + } + } +} + +void CameraBinControl::handleCameraError(int errorCode, const QString &errorString) +{ + emit error(errorCode, errorString); + setState(QCamera::UnloadedState); +} + +void CameraBinControl::delayedReload() +{ +#ifdef CAMEABIN_DEBUG + qDebug() << "CameraBinControl: reload pipeline"; +#endif + if (m_reloadPending) { + m_reloadPending = false; + if (m_state == QCamera::ActiveState && + m_session->isReady() && + m_resourcePolicy->isResourcesGranted()) { + m_session->setState(QCamera::ActiveState); + } + } +} + +bool CameraBinControl::canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(status); + + switch (changeType) { + case QCameraControl::CaptureMode: + case QCameraControl::ImageEncodingSettings: + case QCameraControl::VideoEncodingSettings: + case QCameraControl::Viewfinder: + return true; + default: + return false; + } +} + +#define VIEWFINDER_COLORSPACE_CONVERSION 0x00000004 + +bool CameraBinControl::viewfinderColorSpaceConversion() const +{ + gint flags = 0; + g_object_get(G_OBJECT(m_session->cameraBin()), "flags", &flags, NULL); + + return flags & VIEWFINDER_COLORSPACE_CONVERSION; +} + +void CameraBinControl::setViewfinderColorSpaceConversion(bool enabled) +{ + gint flags = 0; + g_object_get(G_OBJECT(m_session->cameraBin()), "flags", &flags, NULL); + + if (enabled) + flags |= VIEWFINDER_COLORSPACE_CONVERSION; + else + flags &= ~VIEWFINDER_COLORSPACE_CONVERSION; + + g_object_set(G_OBJECT(m_session->cameraBin()), "flags", flags, NULL); +} diff --git a/src/plugins/gstreamer/camerabin/camerabincontrol.h b/src/plugins/gstreamer/camerabin/camerabincontrol.h new file mode 100644 index 000000000..1e5f28e01 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabincontrol.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABINCONTROL_H +#define CAMERABINCONTROL_H + +#include <QHash> +#include <qcameracontrol.h> +#include "camerabinsession.h" + +QT_USE_NAMESPACE + +class CamerabinResourcePolicy; + +class CameraBinControl : public QCameraControl +{ + Q_OBJECT + Q_PROPERTY(bool viewfinderColorSpaceConversion READ viewfinderColorSpaceConversion WRITE setViewfinderColorSpaceConversion) +public: + CameraBinControl( CameraBinSession *session ); + virtual ~CameraBinControl(); + + bool isValid() const { return true; } + + QCamera::State state() const; + void setState(QCamera::State state); + + QCamera::Status status() const { return m_status; } + + QCamera::CaptureMode captureMode() const; + void setCaptureMode(QCamera::CaptureMode mode); + + bool isCaptureModeSupported(QCamera::CaptureMode mode) const; + bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const; + bool viewfinderColorSpaceConversion() const; + +public slots: + void reloadLater(); + void setViewfinderColorSpaceConversion(bool enabled); + +private slots: + void updateStatus(); + void delayedReload(); + + void handleResourcesGranted(); + void handleResourcesLost(); + + void handleBusyChanged(bool); + void handleCameraError(int error, const QString &errorString); + +private: + void updateSupportedResolutions(const QString &device); + + CameraBinSession *m_session; + QCamera::State m_state; + QCamera::Status m_status; + CamerabinResourcePolicy *m_resourcePolicy; + + bool m_reloadPending; +}; + +#endif // CAMERABINCONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinexposure.cpp b/src/plugins/gstreamer/camerabin/camerabinexposure.cpp new file mode 100644 index 000000000..3401d07fc --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinexposure.cpp @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinexposure.h" +#include "camerabinsession.h" +#include <gst/interfaces/photography.h> + +#include <QDebug> + +CameraBinExposure::CameraBinExposure(CameraBinSession *session) + :QCameraExposureControl(session), + m_session(session) +{ +} + +CameraBinExposure::~CameraBinExposure() +{ +} + +QCameraExposure::ExposureMode CameraBinExposure::exposureMode() const +{ + GstSceneMode sceneMode; + gst_photography_get_scene_mode(m_session->photography(), &sceneMode); + + switch (sceneMode) { + case GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT: return QCameraExposure::ExposurePortrait; + case GST_PHOTOGRAPHY_SCENE_MODE_SPORT: return QCameraExposure::ExposureSports; + case GST_PHOTOGRAPHY_SCENE_MODE_NIGHT: return QCameraExposure::ExposureNight; + case GST_PHOTOGRAPHY_SCENE_MODE_MANUAL: return QCameraExposure::ExposureManual; + case GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP: //no direct mapping available so mapping to auto mode + case GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE: //no direct mapping available so mapping to auto mode + case GST_PHOTOGRAPHY_SCENE_MODE_AUTO: + default: + return QCameraExposure::ExposureAuto; + } +} + +void CameraBinExposure::setExposureMode(QCameraExposure::ExposureMode mode) +{ + GstSceneMode sceneMode; + gst_photography_get_scene_mode(m_session->photography(), &sceneMode); + + switch (mode) { + case QCameraExposure::ExposureManual: sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_MANUAL; break; + case QCameraExposure::ExposurePortrait: sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_PORTRAIT; break; + case QCameraExposure::ExposureSports: sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_SPORT; break; + case QCameraExposure::ExposureNight: sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_NIGHT; break; + case QCameraExposure::ExposureAuto: sceneMode = GST_PHOTOGRAPHY_SCENE_MODE_AUTO; break; + default: + break; + } + + gst_photography_set_scene_mode(m_session->photography(), sceneMode); +} + +bool CameraBinExposure::isExposureModeSupported(QCameraExposure::ExposureMode mode) const +{ + //Similar mode names can be found in gst as GstSceneMode + return mode == QCameraExposure::ExposureAuto || + mode == QCameraExposure::ExposurePortrait || + mode == QCameraExposure::ExposureSports || + mode == QCameraExposure::ExposureNight; + + //No direct mapping available for GST_PHOTOGRAPHY_SCENE_MODE_CLOSEUP and + //GST_PHOTOGRAPHY_SCENE_MODE_LANDSCAPE +} + +QCameraExposure::MeteringMode CameraBinExposure::meteringMode() const +{ + return QCameraExposure::MeteringMatrix; +} + +void CameraBinExposure::setMeteringMode(QCameraExposure::MeteringMode mode) +{ + Q_UNUSED(mode); +} + +bool CameraBinExposure::isMeteringModeSupported(QCameraExposure::MeteringMode mode) const +{ + return mode == QCameraExposure::MeteringMatrix; +} + +bool CameraBinExposure::isParameterSupported(ExposureParameter parameter) const +{ + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + case QCameraExposureControl::ISO: + case QCameraExposureControl::Aperture: + case QCameraExposureControl::ShutterSpeed: + return true; + default: + return false; + } +} + +QVariant CameraBinExposure::exposureParameter(ExposureParameter parameter) const +{ + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + { + gfloat ev; + gst_photography_get_ev_compensation(m_session->photography(), &ev); + return QVariant(ev); + } + case QCameraExposureControl::ISO: + { + guint isoSpeed = 0; + gst_photography_get_iso_speed(m_session->photography(), &isoSpeed); + return QVariant(isoSpeed); + } + case QCameraExposureControl::Aperture: + return QVariant(2.8); + case QCameraExposureControl::ShutterSpeed: + { + guint32 shutterSpeed = 0; + gst_photography_get_exposure(m_session->photography(), &shutterSpeed); + + return QVariant(shutterSpeed/1000000.0); + } + default: + return QVariant(); + } +} + +QCameraExposureControl::ParameterFlags CameraBinExposure::exposureParameterFlags(ExposureParameter parameter) const +{ + QCameraExposureControl::ParameterFlags flags = 0; + + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + flags |= ContinuousRange; + break; + case QCameraExposureControl::Aperture: + flags |= ReadOnly; + break; + default: + break; + } + + return flags; +} + +QVariantList CameraBinExposure::supportedParameterRange(ExposureParameter parameter) const +{ + QVariantList res; + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + res << -2.0 << 2.0; + break; + case QCameraExposureControl::ISO: + res << 100 << 200 << 400; + break; + case QCameraExposureControl::Aperture: + res << 2.8; + break; + default: + break; + } + + return res; +} + +bool CameraBinExposure::setExposureParameter(ExposureParameter parameter, const QVariant& value) +{ + QVariant oldValue = exposureParameter(parameter); + + switch (parameter) { + case QCameraExposureControl::ExposureCompensation: + gst_photography_set_ev_compensation(m_session->photography(), value.toReal()); + break; + case QCameraExposureControl::ISO: + gst_photography_set_iso_speed(m_session->photography(), value.toInt()); + break; + case QCameraExposureControl::Aperture: + gst_photography_set_aperture(m_session->photography(), guint(value.toReal()*1000000)); + break; + case QCameraExposureControl::ShutterSpeed: + gst_photography_set_exposure(m_session->photography(), guint(value.toReal()*1000000)); + break; + default: + return false; + } + + QVariant newValue = exposureParameter(parameter); + if (!qFuzzyCompare(oldValue.toReal(), newValue.toReal())) + emit exposureParameterChanged(parameter); + + return true; +} + +QString CameraBinExposure::extendedParameterName(ExposureParameter) +{ + return QString(); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinexposure.h b/src/plugins/gstreamer/camerabin/camerabinexposure.h new file mode 100644 index 000000000..44439253d --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinexposure.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINEXPOSURECONTROL_MAEMO_H +#define CAMERABINEXPOSURECONTROL_MAEMO_H + +#include <qcamera.h> +#include <qcameraexposurecontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +class CameraBinSession; + +QT_USE_NAMESPACE + +class Q_MULTIMEDIA_EXPORT CameraBinExposure : public QCameraExposureControl +{ + Q_OBJECT + +public: + CameraBinExposure(CameraBinSession *session); + virtual ~CameraBinExposure(); + + QCameraExposure::ExposureMode exposureMode() const; + void setExposureMode(QCameraExposure::ExposureMode mode); + bool isExposureModeSupported(QCameraExposure::ExposureMode mode) const; + + QCameraExposure::MeteringMode meteringMode() const; + void setMeteringMode(QCameraExposure::MeteringMode mode); + bool isMeteringModeSupported(QCameraExposure::MeteringMode mode) const; + + bool isParameterSupported(ExposureParameter parameter) const; + QVariant exposureParameter(ExposureParameter parameter) const; + ParameterFlags exposureParameterFlags(ExposureParameter parameter) const; + QVariantList supportedParameterRange(ExposureParameter parameter) const; + bool setExposureParameter(ExposureParameter parameter, const QVariant& value); + + QString extendedParameterName(ExposureParameter parameter); + +private: + CameraBinSession *m_session; +}; + +#endif // CAMERABINEXPOSURECONTROL_MAEMO_H diff --git a/src/plugins/gstreamer/camerabin/camerabinflash.cpp b/src/plugins/gstreamer/camerabin/camerabinflash.cpp new file mode 100644 index 000000000..f5b76524d --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinflash.cpp @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinflash.h" +#include "camerabinsession.h" +#include <gst/interfaces/photography.h> + +#include <QDebug> + +CameraBinFlash::CameraBinFlash(CameraBinSession *session) + :QCameraFlashControl(session), + m_session(session) +{ +} + +CameraBinFlash::~CameraBinFlash() +{ +} + +QCameraExposure::FlashModes CameraBinFlash::flashMode() const +{ + GstFlashMode flashMode; + gst_photography_get_flash_mode(m_session->photography(), &flashMode); + + QCameraExposure::FlashModes modes; + switch (flashMode) { + case GST_PHOTOGRAPHY_FLASH_MODE_AUTO: modes |= QCameraExposure::FlashAuto; break; + case GST_PHOTOGRAPHY_FLASH_MODE_OFF: modes |= QCameraExposure::FlashOff; break; + case GST_PHOTOGRAPHY_FLASH_MODE_ON: modes |= QCameraExposure::FlashOn; break; + case GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN: modes |= QCameraExposure::FlashFill; break; + case GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE: modes |= QCameraExposure::FlashRedEyeReduction; break; + default: + modes |= QCameraExposure::FlashAuto; + break; + } + return modes; +} + +void CameraBinFlash::setFlashMode(QCameraExposure::FlashModes mode) +{ + GstFlashMode flashMode; + gst_photography_get_flash_mode(m_session->photography(), &flashMode); + + if (mode.testFlag(QCameraExposure::FlashAuto)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_AUTO; + else if (mode.testFlag(QCameraExposure::FlashOff)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_OFF; + else if (mode.testFlag(QCameraExposure::FlashOn)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_ON; + else if (mode.testFlag(QCameraExposure::FlashFill)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_FILL_IN; + else if (mode.testFlag(QCameraExposure::FlashRedEyeReduction)) flashMode = GST_PHOTOGRAPHY_FLASH_MODE_RED_EYE; + + gst_photography_set_flash_mode(m_session->photography(), flashMode); +} + +bool CameraBinFlash::isFlashModeSupported(QCameraExposure::FlashModes mode) const +{ + return mode == QCameraExposure::FlashOff || + mode == QCameraExposure::FlashOn || + mode == QCameraExposure::FlashAuto || + mode == QCameraExposure::FlashRedEyeReduction || + mode == QCameraExposure::FlashFill; +} + +bool CameraBinFlash::isFlashReady() const +{ + return true; +} + diff --git a/src/plugins/gstreamer/camerabin/camerabinflash.h b/src/plugins/gstreamer/camerabin/camerabinflash.h new file mode 100644 index 000000000..7c566becf --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinflash.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINFLASHCONTROL_H +#define CAMERABINFLASHCONTROL_H + +#include <qcamera.h> +#include <qcameraflashcontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +class CameraBinSession; + +QT_USE_NAMESPACE + +class Q_MULTIMEDIA_EXPORT CameraBinFlash : public QCameraFlashControl +{ + Q_OBJECT +public: + CameraBinFlash(CameraBinSession *session); + virtual ~CameraBinFlash(); + + QCameraExposure::FlashModes flashMode() const; + void setFlashMode(QCameraExposure::FlashModes mode); + bool isFlashModeSupported(QCameraExposure::FlashModes mode) const; + + bool isFlashReady() const; + +private: + CameraBinSession *m_session; +}; + +#endif // CAMERABINFLASHCONTROL_H + diff --git a/src/plugins/gstreamer/camerabin/camerabinfocus.cpp b/src/plugins/gstreamer/camerabin/camerabinfocus.cpp new file mode 100644 index 000000000..cf7c9ecd8 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinfocus.cpp @@ -0,0 +1,245 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinfocus.h" +#include "camerabinsession.h" + +#include <gst/interfaces/photography.h> + +#include <QDebug> +#include <QtCore/qmetaobject.h> + +//#define CAMERABIN_DEBUG 1 +#define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + +CameraBinFocus::CameraBinFocus(CameraBinSession *session) + :QCameraFocusControl(session), + m_session(session), + m_focusMode(QCameraFocus::AutoFocus), + m_focusStatus(QCamera::Unlocked), + m_focusZoneStatus(QCameraFocusZone::Selected) +{ + connect(m_session, SIGNAL(stateChanged(QCamera::State)), + this, SLOT(_q_handleCameraStateChange(QCamera::State))); + connect(m_session, SIGNAL(imageCaptured(int,QImage)), + this, SLOT(_q_handleCapturedImage())); +} + +CameraBinFocus::~CameraBinFocus() +{ +} + +QCameraFocus::FocusMode CameraBinFocus::focusMode() const +{ + return m_focusMode; +} + +void CameraBinFocus::setFocusMode(QCameraFocus::FocusMode mode) +{ + if (isFocusModeSupported(mode)) { + m_focusMode = mode; + } +} + +bool CameraBinFocus::isFocusModeSupported(QCameraFocus::FocusMode mode) const +{ + return mode & QCameraFocus::AutoFocus; +} + +qreal CameraBinFocus::maximumOpticalZoom() const +{ + return 1.0; +} + +qreal CameraBinFocus::maximumDigitalZoom() const +{ + return 10; +} + +qreal CameraBinFocus::opticalZoom() const +{ + return 1.0; +} + +qreal CameraBinFocus::digitalZoom() const +{ +#ifdef Q_WS_MAEMO_5 + gint zoomFactor = 0; + g_object_get(GST_BIN(m_session->cameraBin()), "zoom", &zoomFactor, NULL); + return zoomFactor/100.0; +#else + gfloat zoomFactor = 1.0; + g_object_get(GST_BIN(m_session->cameraBin()), "zoom", &zoomFactor, NULL); + return zoomFactor; +#endif +} + +void CameraBinFocus::zoomTo(qreal optical, qreal digital) +{ + Q_UNUSED(optical); + digital = qBound(qreal(1.0), digital, qreal(10.0)); +#ifdef Q_WS_MAEMO_5 + g_object_set(GST_BIN(m_session->cameraBin()), "zoom", qRound(digital*100.0), NULL); +#else + g_object_set(GST_BIN(m_session->cameraBin()), "zoom", digital, NULL); +#endif + emit digitalZoomChanged(digital); +} + +QCameraFocus::FocusPointMode CameraBinFocus::focusPointMode() const +{ + return QCameraFocus::FocusPointAuto; +} + +void CameraBinFocus::setFocusPointMode(QCameraFocus::FocusPointMode mode) +{ + Q_UNUSED(mode); +} + +bool CameraBinFocus::isFocusPointModeSupported(QCameraFocus::FocusPointMode mode) const +{ + return mode == QCameraFocus::FocusPointAuto; +} + +QPointF CameraBinFocus::customFocusPoint() const +{ + return QPointF(0.5, 0.5); +} + +void CameraBinFocus::setCustomFocusPoint(const QPointF &point) +{ + Q_UNUSED(point); +} + +QCameraFocusZoneList CameraBinFocus::focusZones() const +{ + return QCameraFocusZoneList() << QCameraFocusZone(QRectF(0.35, 0.35, 0.3, 0.3), m_focusZoneStatus); +} + + +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)) { + gint status = GST_PHOTOGRAPHY_FOCUS_STATUS_NONE; + gst_structure_get_int (gm->structure, "status", &status); + QCamera::LockStatus focusStatus = m_focusStatus; + QCamera::LockChangeReason reason = QCamera::UserRequest; + + switch (status) { + case GST_PHOTOGRAPHY_FOCUS_STATUS_FAIL: + focusStatus = QCamera::Unlocked; + reason = QCamera::LockFailed; + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_SUCCESS: + focusStatus = QCamera::Locked; + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_NONE: + break; + case GST_PHOTOGRAPHY_FOCUS_STATUS_RUNNING: + focusStatus = QCamera::Searching; + break; + default: + break; + } + + static int signalIndex = metaObject()->indexOfSlot( + "_q_setFocusStatus(QCamera::LockStatus,QCamera::LockChangeReason)"); + metaObject()->method(signalIndex).invoke(this, + Qt::QueuedConnection, + Q_ARG(QCamera::LockStatus,focusStatus), + Q_ARG(QCamera::LockChangeReason,reason)); + } +} + +void CameraBinFocus::_q_setFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ +#ifdef CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Current:" + << ENUM_NAME(QCamera, "LockStatus", m_focusStatus) + << "New:" + << ENUM_NAME(QCamera, "LockStatus", status) << ENUM_NAME(QCamera, "LockChangeReason", reason); +#endif + + if (m_focusStatus != status) { + m_focusStatus = status; + + QCameraFocusZone::FocusZoneStatus zonesStatus = + m_focusStatus == QCamera::Locked ? + QCameraFocusZone::Focused : QCameraFocusZone::Selected; + + if (m_focusZoneStatus != zonesStatus) { + m_focusZoneStatus = zonesStatus; + emit focusZonesChanged(); + } + + emit _q_focusStatusChanged(m_focusStatus, reason); + } +} + +void CameraBinFocus::_q_handleCameraStateChange(QCamera::State state) +{ + if (state != QCamera::ActiveState) + _q_setFocusStatus(QCamera::Unlocked, QCamera::LockLost); +} + +void CameraBinFocus::_q_handleCapturedImage() +{ +#ifdef Q_WS_MAEMO_5 + //N900 lost focus after image capture + if (m_focusStatus != QCamera::Unlocked) { + m_focusStatus = QCamera::Unlocked; + emit _q_focusStatusChanged(QCamera::Unlocked, QCamera::LockLost); + } +#endif +} + +void CameraBinFocus::_q_startFocusing() +{ + _q_setFocusStatus(QCamera::Searching, QCamera::UserRequest); + gst_photography_set_autofocus(m_session->photography(), TRUE); +} + +void CameraBinFocus::_q_stopFocusing() +{ + gst_photography_set_autofocus(m_session->photography(), FALSE); + _q_setFocusStatus(QCamera::Unlocked, QCamera::UserRequest); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinfocus.h b/src/plugins/gstreamer/camerabin/camerabinfocus.h new file mode 100644 index 000000000..e496d2e3b --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinfocus.h @@ -0,0 +1,104 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINFOCUSCONTROL_H +#define CAMERABINFOCUSCONTROL_H + +#include <qcamera.h> +#include <qcamerafocuscontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +class CameraBinSession; + +QT_USE_NAMESPACE + +class CameraBinFocus : public QCameraFocusControl +{ + Q_OBJECT + +public: + CameraBinFocus(CameraBinSession *session); + virtual ~CameraBinFocus(); + + QCameraFocus::FocusMode focusMode() const; + void setFocusMode(QCameraFocus::FocusMode mode); + bool isFocusModeSupported(QCameraFocus::FocusMode mode) const; + + qreal maximumOpticalZoom() const; + qreal maximumDigitalZoom() const; + qreal opticalZoom() const; + qreal digitalZoom() const; + + void zoomTo(qreal optical, qreal digital) ; + + QCameraFocus::FocusPointMode focusPointMode() const; + void setFocusPointMode(QCameraFocus::FocusPointMode mode) ; + bool isFocusPointModeSupported(QCameraFocus::FocusPointMode) const; + QPointF customFocusPoint() const; + void setCustomFocusPoint(const QPointF &point); + + QCameraFocusZoneList focusZones() const; + + void handleFocusMessage(GstMessage*); + QCamera::LockStatus focusStatus() const { return m_focusStatus; } + +Q_SIGNALS: + void _q_focusStatusChanged(QCamera::LockStatus status, QCamera::LockChangeReason reason); + +public Q_SLOTS: + void _q_startFocusing(); + void _q_stopFocusing(); + +private Q_SLOTS: + void _q_setFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + void _q_handleCameraStateChange(QCamera::State state); + void _q_handleCapturedImage(); + +private: + CameraBinSession *m_session; + QCameraFocus::FocusMode m_focusMode; + QCamera::LockStatus m_focusStatus; + QCameraFocusZone::FocusZoneStatus m_focusZoneStatus; +}; + +#endif // CAMERABINFOCUSCONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp b/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp new file mode 100644 index 000000000..3df1105bc --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimagecapture.cpp @@ -0,0 +1,347 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinimagecapture.h" +#include "camerabincapturedestination.h" +#include "camerabincapturebufferformat.h" +#include "camerabinsession.h" +#include "qgstvideobuffer.h" +#include "qvideosurfacegstsink.h" +#include "qgstutils.h" +#include <QtCore/qdebug.h> +#include <QtCore/qbuffer.h> +#include <QtGui/qimagereader.h> + +//#define DEBUG_CAPTURE + +#ifdef Q_WS_MAEMO_5 +#define IMAGE_DONE_SIGNAL "img-done" +#else +#define IMAGE_DONE_SIGNAL "image-done" +#endif + + +Q_DECLARE_METATYPE(QVideoFrame) +Q_DECLARE_METATYPE(QtMultimediaKit::MetaData) + +namespace +{ +class CameraRegisterMetaTypes +{ +public: + CameraRegisterMetaTypes() + { + qRegisterMetaType<QVideoFrame>("QVideoFrame"); + qRegisterMetaType<QtMultimediaKit::MetaData>("QtMultimediaKit::MetaData"); + } +} _registerCameraMetaTypes; +} + + +CameraBinImageCapture::CameraBinImageCapture(CameraBinSession *session) + :QCameraImageCaptureControl(session) + , m_session(session) + , m_ready(false) + , m_requestId(0) + , m_jpegEncoderElement(0) + , m_metadataMuxerElement(0) +{ + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateState())); + connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int))); + connect(m_session, SIGNAL(imageCaptured(int,QImage)), this, SIGNAL(imageCaptured(int,QImage))); + connect(m_session, SIGNAL(busMessage(QGstreamerMessage)), SLOT(handleBusMessage(QGstreamerMessage))); + + g_signal_connect(G_OBJECT(m_session->cameraBin()), IMAGE_DONE_SIGNAL, G_CALLBACK(handleImageSaved), this); +} + +CameraBinImageCapture::~CameraBinImageCapture() +{ +} + +bool CameraBinImageCapture::isReadyForCapture() const +{ + return m_ready; +} + +int CameraBinImageCapture::capture(const QString &fileName) +{ + m_requestId++; + + if (!m_ready) { + emit error(m_requestId, QCameraImageCapture::NotReadyError, tr("Camera not ready")); + return m_requestId; + } + +#ifdef DEBUG_CAPTURE + qDebug() << Q_FUNC_INFO << m_requestId << fileName; +#endif + m_session->captureImage(m_requestId, fileName); + return m_requestId; +} + +void CameraBinImageCapture::cancelCapture() +{ +} + +void CameraBinImageCapture::updateState() +{ + bool ready = m_session->state() == QCamera::ActiveState; + if (m_ready != ready) { +#ifdef DEBUG_CAPTURE + qDebug() << "readyForCaptureChanged" << ready; +#endif + emit readyForCaptureChanged(m_ready = ready); + } +} + +gboolean CameraBinImageCapture::handleImageSaved(GstElement *camera, + const gchar *filename, + CameraBinImageCapture *self) +{ +#ifdef DEBUG_CAPTURE + qDebug() << "Image saved" << filename; +#endif + + Q_UNUSED(camera); + + if (self->m_session->captureDestinationControl()->captureDestination() & QCameraImageCapture::CaptureToFile) { + QMetaObject::invokeMethod(self, "imageSaved", + Qt::QueuedConnection, + Q_ARG(int, self->m_requestId), + Q_ARG(QString, QString::fromUtf8(filename))); + } else { +#ifdef DEBUG_CAPTURE + qDebug() << Q_FUNC_INFO << "Dropped saving file" << filename; +#endif + //camerabin creates an empty file when captured buffer is dropped, + //let's remove it + QFileInfo info(QString::fromUtf8(filename)); + if (info.isFile() && + info.filePath().startsWith("/home") && + info.size() == 0) { + QFile(info.absoluteFilePath()).remove(); + } + } + return true; +} + +gboolean CameraBinImageCapture::metadataEventProbe(GstPad *pad, GstEvent *event, CameraBinImageCapture *self) +{ + + if (GST_EVENT_TYPE(event) == GST_EVENT_TAG) { + GstTagList *gstTags; + gst_event_parse_tag(event, &gstTags); + QMap<QByteArray, QVariant> extendedTags = QGstUtils::gstTagListToMap(gstTags); + +#ifdef DEBUG_CAPTURE + qDebug() << QString(gst_structure_to_string(gst_event_get_structure(event))).right(768); + qDebug() << "Capture event probe" << extendedTags; +#endif + + QMap<QtMultimediaKit::MetaData, QVariant> tags; + tags[QtMultimediaKit::ISOSpeedRatings] = extendedTags.value("capturing-iso-speed"); + tags[QtMultimediaKit::DigitalZoomRatio] = extendedTags.value("capturing-digital-zoom-ratio"); + tags[QtMultimediaKit::ExposureTime] = extendedTags.value("capturing-shutter-speed"); + tags[QtMultimediaKit::WhiteBalance] = extendedTags.value("capturing-white-balance"); + tags[QtMultimediaKit::Flash] = extendedTags.value("capturing-flash-fired"); + tags[QtMultimediaKit::FocalLengthIn35mmFilm] = extendedTags.value("capturing-focal-length"); + tags[QtMultimediaKit::MeteringMode] = extendedTags.value("capturing-metering-mode"); + tags[QtMultimediaKit::ExposureMode] = extendedTags.value("capturing-exposure-mode"); + tags[QtMultimediaKit::FNumber] = extendedTags.value("capturing-focal-ratio"); + tags[QtMultimediaKit::ExposureMode] = extendedTags.value("capturing-exposure-mode"); + + QMapIterator<QtMultimediaKit::MetaData, QVariant> i(tags); + while (i.hasNext()) { + i.next(); + if (i.value().isValid()) { + QMetaObject::invokeMethod(self, "imageMetadataAvailable", + Qt::QueuedConnection, + Q_ARG(int, self->m_requestId), + Q_ARG(QtMultimediaKit::MetaData, i.key()), + Q_ARG(QVariant, i.value())); + } + } + } + + return true; +} + +gboolean CameraBinImageCapture::uncompressedBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *self) +{ + Q_UNUSED(pad); + CameraBinSession *session = self->m_session; + +#ifdef DEBUG_CAPTURE + qDebug() << "Uncompressed buffer probe" << gst_caps_to_string(GST_BUFFER_CAPS(buffer)); +#endif + + QCameraImageCapture::CaptureDestinations destination = + session->captureDestinationControl()->captureDestination(); + QVideoFrame::PixelFormat format = session->captureBufferFormatControl()->bufferFormat(); + + 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); + + QVideoFrame frame(videoBuffer, + format.frameSize(), + format.pixelFormat()); + + QMetaObject::invokeMethod(self, "imageAvailable", + Qt::QueuedConnection, + Q_ARG(int, self->m_requestId), + Q_ARG(QVideoFrame, frame)); + } + } + + //keep the buffer if capture to file or jpeg buffer capture was reuqsted + bool keepBuffer = (destination & QCameraImageCapture::CaptureToFile) || + ((destination & QCameraImageCapture::CaptureToBuffer) && + format == QVideoFrame::Format_Jpeg); + + return keepBuffer; +} + +gboolean CameraBinImageCapture::jpegBufferProbe(GstPad *pad, GstBuffer *buffer, CameraBinImageCapture *self) +{ + Q_UNUSED(pad); + CameraBinSession *session = self->m_session; + +#ifdef DEBUG_CAPTURE + qDebug() << "Jpeg buffer probe" << gst_caps_to_string(GST_BUFFER_CAPS(buffer)); +#endif + + 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)); + //if resolution is not presented in caps, try to find it from encoded jpeg data: + if (resolution.isEmpty()) { + QBuffer data; + data.setData(reinterpret_cast<const char*>(GST_BUFFER_DATA(buffer)), GST_BUFFER_SIZE(buffer)); + QImageReader reader(&data, "JPEG"); + resolution = reader.size(); + } + + QVideoFrame frame(videoBuffer, + resolution, + QVideoFrame::Format_Jpeg); + + QMetaObject::invokeMethod(self, "imageAvailable", + Qt::QueuedConnection, + Q_ARG(int, self->m_requestId), + Q_ARG(QVideoFrame, frame)); + } + + //drop the buffer if capture to file was disabled + return destination & QCameraImageCapture::CaptureToFile; +} + +void CameraBinImageCapture::handleBusMessage(const QGstreamerMessage &message) +{ + //Install metadata event and buffer probes + + //The image capture pipiline is built dynamically, + //it's necessary to wait until jpeg encoder is added to pipeline + + GstMessage *gm = message.rawMessage(); + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) { + GstState oldState; + GstState newState; + GstState pending; + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + if (newState == GST_STATE_READY) { + GstElement *element = GST_ELEMENT(GST_MESSAGE_SRC(gm)); + if (!element) + return; + + QString elementName = QString::fromLatin1(gst_element_get_name(element)); + if (elementName.contains("jpegenc") && element != m_jpegEncoderElement) { + m_jpegEncoderElement = element; + GstPad *sinkpad = gst_element_get_static_pad(element, "sink"); + + //metadata event probe is installed before jpeg encoder + //to emit metadata available signal as soon as possible. +#ifdef DEBUG_CAPTURE + qDebug() << "install metadata probe"; +#endif + gst_pad_add_event_probe(sinkpad, + G_CALLBACK(CameraBinImageCapture::metadataEventProbe), + this); + +#ifdef DEBUG_CAPTURE + qDebug() << "install uncompressed buffer probe"; +#endif + gst_pad_add_buffer_probe(sinkpad, + G_CALLBACK(CameraBinImageCapture::uncompressedBufferProbe), + this); + + gst_object_unref(sinkpad); + } else if ((elementName.contains("jifmux") || 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 + m_metadataMuxerElement = element; + + GstPad *srcpad = gst_element_get_static_pad(element, "src"); +#ifdef DEBUG_CAPTURE + qDebug() << "install jpeg buffer probe"; +#endif + gst_pad_add_buffer_probe(srcpad, + G_CALLBACK(CameraBinImageCapture::jpegBufferProbe), + this); + gst_object_unref(srcpad); + } + } + } +} diff --git a/src/plugins/gstreamer/camerabin/camerabinimagecapture.h b/src/plugins/gstreamer/camerabin/camerabinimagecapture.h new file mode 100644 index 000000000..4aa5e998f --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimagecapture.h @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABINIMAGECAPTURECONTROL_H +#define CAMERABINIMAGECAPTURECONTROL_H + +#include <qcameraimagecapturecontrol.h> +#include "camerabinsession.h" + +QT_USE_NAMESPACE + +class CameraBinImageCapture : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + CameraBinImageCapture(CameraBinSession *session); + virtual ~CameraBinImageCapture(); + + QCameraImageCapture::DriveMode driveMode() const { return QCameraImageCapture::SingleImageCapture; } + void setDriveMode(QCameraImageCapture::DriveMode) {} + + bool isReadyForCapture() const; + int capture(const QString &fileName); + void cancelCapture(); + +private slots: + void updateState(); + void handleBusMessage(const QGstreamerMessage &message); + +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 *); + static gboolean handleImageSaved(GstElement *camera, const gchar *filename, CameraBinImageCapture *); + + CameraBinSession *m_session; + bool m_ready; + int m_requestId; + GstElement *m_jpegEncoderElement; + GstElement *m_metadataMuxerElement; +}; + +#endif // CAMERABINCAPTURECORNTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinimageencoder.cpp b/src/plugins/gstreamer/camerabin/camerabinimageencoder.cpp new file mode 100644 index 000000000..2def12666 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimageencoder.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinimageencoder.h" +#include "camerabinsession.h" + +#include <QtCore/qdebug.h> + +CameraBinImageEncoder::CameraBinImageEncoder(CameraBinSession *session) + :QImageEncoderControl(session), m_session(session) +{ +} + +CameraBinImageEncoder::~CameraBinImageEncoder() +{ +} + +QList<QSize> CameraBinImageEncoder::supportedResolutions(const QImageEncoderSettings &, bool *continuous) const +{ + qDebug() << "CameraBinImageEncoder::supportedResolutions()"; + if (continuous) + *continuous = false; + + return m_session->supportedResolutions(qMakePair<int,int>(0,0), continuous, QCamera::CaptureStillImage); +} + +QStringList CameraBinImageEncoder::supportedImageCodecs() const +{ + return QStringList() << "jpeg"; +} + +QString CameraBinImageEncoder::imageCodecDescription(const QString &codecName) const +{ + if (codecName == "jpeg") + return tr("JPEG image"); + + return QString(); +} + +QImageEncoderSettings CameraBinImageEncoder::imageSettings() const +{ + return m_settings; +} + +void CameraBinImageEncoder::setImageSettings(const QImageEncoderSettings &settings) +{ + m_settings = settings; + emit settingsChanged(); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinimageencoder.h b/src/plugins/gstreamer/camerabin/camerabinimageencoder.h new file mode 100644 index 000000000..ddb06a668 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimageencoder.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINIMAGEENCODE_H +#define CAMERABINIMAGEENCODE_H + +class CameraBinSession; + +#include <qimageencodercontrol.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> + +#include <gst/gst.h> +QT_USE_NAMESPACE + +class CameraBinImageEncoder : public QImageEncoderControl +{ + Q_OBJECT +public: + CameraBinImageEncoder(CameraBinSession *session); + virtual ~CameraBinImageEncoder(); + + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings = QImageEncoderSettings(), + bool *continuous = 0) const; + + QStringList supportedImageCodecs() const; + QString imageCodecDescription(const QString &formatName) const; + + QImageEncoderSettings imageSettings() const; + void setImageSettings(const QImageEncoderSettings &settings); + +Q_SIGNALS: + void settingsChanged(); + +private: + QImageEncoderSettings m_settings; + + CameraBinSession *m_session; + + // Added + QStringList m_codecs; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QStringList> m_codecOptions; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabin/camerabinimageprocessing.cpp b/src/plugins/gstreamer/camerabin/camerabinimageprocessing.cpp new file mode 100644 index 000000000..075ff2a67 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimageprocessing.cpp @@ -0,0 +1,171 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinimageprocessing.h" +#include "camerabinsession.h" + +CameraBinImageProcessing::CameraBinImageProcessing(CameraBinSession *session) + :QCameraImageProcessingControl(session), + m_session(session) +{ + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_AUTO] = QCameraImageProcessing::WhiteBalanceAuto; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_DAYLIGHT] = QCameraImageProcessing::WhiteBalanceSunlight; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_CLOUDY] = QCameraImageProcessing::WhiteBalanceCloudy; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_SUNSET] = QCameraImageProcessing::WhiteBalanceSunset; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_TUNGSTEN] = QCameraImageProcessing::WhiteBalanceTungsten; + m_mappedWbValues[GST_PHOTOGRAPHY_WB_MODE_FLUORESCENT] = QCameraImageProcessing::WhiteBalanceFluorescent; + + updateColorBalanceValues(); +} + +CameraBinImageProcessing::~CameraBinImageProcessing() +{ +} + +void CameraBinImageProcessing::updateColorBalanceValues() +{ + if (!GST_IS_COLOR_BALANCE(m_session->cameraBin())) { + // Camerabin doesn't implement gstcolorbalance interface + return; + } + + GstColorBalance *balance = GST_COLOR_BALANCE(m_session->cameraBin()); + const GList *controls = gst_color_balance_list_channels(balance); + + const GList *item; + GstColorBalanceChannel *channel; + gint cur_value; + + for (item = controls; item; item = g_list_next (item)) { + channel = (GstColorBalanceChannel *)item->data; + cur_value = gst_color_balance_get_value (balance, channel); + + if (!g_ascii_strcasecmp (channel->label, "brightness")) { + m_values[QCameraImageProcessingControl::Brightness] = cur_value; + } else if (!g_ascii_strcasecmp (channel->label, "contrast")) { + m_values[QCameraImageProcessingControl::Contrast] = cur_value; + } else if (!g_ascii_strcasecmp (channel->label, "saturation")) { + m_values[QCameraImageProcessingControl::Saturation] = cur_value; + } + } +} + +bool CameraBinImageProcessing::setColorBalanceValue(const QString& channel, int value) +{ + + if (!GST_IS_COLOR_BALANCE(m_session->cameraBin())) { + // Camerabin doesn't implement gstcolorbalance interface + return false; + } + + GstColorBalance *balance = GST_COLOR_BALANCE(m_session->cameraBin()); + const GList *controls = gst_color_balance_list_channels(balance); + + const GList *item; + GstColorBalanceChannel *colorBalanceChannel; + + for (item = controls; item; item = g_list_next (item)) { + colorBalanceChannel = (GstColorBalanceChannel *)item->data; + + if (!g_ascii_strcasecmp (colorBalanceChannel->label, channel.toAscii())) { + gst_color_balance_set_value (balance, colorBalanceChannel, value); + return true; + } + } + + return false; +} + +QCameraImageProcessing::WhiteBalanceMode CameraBinImageProcessing::whiteBalanceMode() const +{ + GstWhiteBalanceMode wbMode; + gst_photography_get_white_balance_mode(m_session->photography(), &wbMode); + return m_mappedWbValues[wbMode]; +} + +void CameraBinImageProcessing::setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode mode) +{ + if (isWhiteBalanceModeSupported(mode)) + gst_photography_set_white_balance_mode(m_session->photography(), m_mappedWbValues.key(mode)); +} + +bool CameraBinImageProcessing::isWhiteBalanceModeSupported(QCameraImageProcessing::WhiteBalanceMode mode) const +{ + return m_mappedWbValues.values().contains(mode); +} + +bool CameraBinImageProcessing::isProcessingParameterSupported(QCameraImageProcessingControl::ProcessingParameter parameter) const +{ + return parameter == QCameraImageProcessingControl::Contrast + || parameter == QCameraImageProcessingControl::Brightness + || parameter == QCameraImageProcessingControl::Saturation; +} + +QVariant CameraBinImageProcessing::processingParameter( + QCameraImageProcessingControl::ProcessingParameter parameter) const +{ + if (m_values.contains(parameter)) + return m_values.value(parameter); + else + return QVariant(); +} + +void CameraBinImageProcessing::setProcessingParameter( + QCameraImageProcessingControl::ProcessingParameter parameter, + QVariant value) +{ + switch (parameter) { + case Contrast: + setColorBalanceValue("contrast", value.toInt()); + break; + case Brightness: + setColorBalanceValue("brightness", value.toInt()); + break; + case Saturation: + setColorBalanceValue("saturation", value.toInt()); + break; + default: + break; + } + + updateColorBalanceValues(); +} + diff --git a/src/plugins/gstreamer/camerabin/camerabinimageprocessing.h b/src/plugins/gstreamer/camerabin/camerabinimageprocessing.h new file mode 100644 index 000000000..661d0d9d4 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinimageprocessing.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINIMAGEPROCESSINGCONTROL_H +#define CAMERABINIMAGEPROCESSINGCONTROL_H + +#include <qcamera.h> +#include <qcameraimageprocessingcontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +#include <gst/interfaces/photography.h> +#include <gst/interfaces/colorbalance.h> + +class CameraBinSession; + +QT_USE_NAMESPACE + +class CameraBinImageProcessing : public QCameraImageProcessingControl +{ + Q_OBJECT + +public: + CameraBinImageProcessing(CameraBinSession *session); + virtual ~CameraBinImageProcessing(); + + QCameraImageProcessing::WhiteBalanceMode whiteBalanceMode() const; + void setWhiteBalanceMode(QCameraImageProcessing::WhiteBalanceMode mode); + bool isWhiteBalanceModeSupported(QCameraImageProcessing::WhiteBalanceMode mode) const; + + bool isProcessingParameterSupported(ProcessingParameter) const; + QVariant processingParameter(ProcessingParameter parameter) const; + void setProcessingParameter(ProcessingParameter parameter, QVariant value); + +private: + bool setColorBalanceValue(const QString& channel, int value); + void updateColorBalanceValues(); + +private: + CameraBinSession *m_session; + QMap<QCameraImageProcessingControl::ProcessingParameter, int> m_values; + QMap<GstWhiteBalanceMode, QCameraImageProcessing::WhiteBalanceMode> m_mappedWbValues; +}; + +#endif // CAMERABINIMAGEPROCESSINGCONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinlocks.cpp b/src/plugins/gstreamer/camerabin/camerabinlocks.cpp new file mode 100644 index 000000000..66da126f8 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinlocks.cpp @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinlocks.h" +#include "camerabinsession.h" +#include "camerabinfocus.h" + +#include <gst/interfaces/photography.h> + +#include <QDebug> + +CameraBinLocks::CameraBinLocks(CameraBinSession *session) + :QCameraLocksControl(session), + m_session(session), + m_focus(m_session->cameraFocusControl()) +{ + connect(m_focus, SIGNAL(_q_focusStatusChanged(QCamera::LockStatus, QCamera::LockChangeReason)), + this, SLOT(updateFocusStatus(QCamera::LockStatus, QCamera::LockChangeReason))); +} + +CameraBinLocks::~CameraBinLocks() +{ +} + +QCamera::LockTypes CameraBinLocks::supportedLocks() const +{ + return QCamera::LockFocus; +} + +QCamera::LockStatus CameraBinLocks::lockStatus(QCamera::LockType lock) const +{ + return lock == QCamera::LockFocus ? m_focus->focusStatus() : QCamera::Unlocked; +} + +void CameraBinLocks::searchAndLock(QCamera::LockTypes locks) +{ + if (locks & QCamera::LockFocus) + m_focus->_q_startFocusing(); +} + +void CameraBinLocks::unlock(QCamera::LockTypes locks) +{ + if (locks & QCamera::LockFocus) + m_focus->_q_stopFocusing(); +} + +void CameraBinLocks::updateFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason) +{ + emit lockStatusChanged(QCamera::LockFocus, status, reason); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinlocks.h b/src/plugins/gstreamer/camerabin/camerabinlocks.h new file mode 100644 index 000000000..29c4f2ece --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinlocks.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINLOCKSCONTROL_H +#define CAMERABINLOCKSCONTROL_H + +#include <qcamera.h> +#include <qcameralockscontrol.h> + +#include <gst/gst.h> +#include <glib.h> + +class CameraBinSession; +class CameraBinFocus; + +QT_USE_NAMESPACE + +class CameraBinLocks : public QCameraLocksControl +{ + Q_OBJECT + +public: + CameraBinLocks(CameraBinSession *session); + virtual ~CameraBinLocks(); + + QCamera::LockTypes supportedLocks() const; + + QCamera::LockStatus lockStatus(QCamera::LockType lock) const; + + void searchAndLock(QCamera::LockTypes locks); + void unlock(QCamera::LockTypes locks); + +private slots: + void updateFocusStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason); + +private: + CameraBinSession *m_session; + CameraBinFocus *m_focus; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabin/camerabinmetadata.cpp b/src/plugins/gstreamer/camerabin/camerabinmetadata.cpp new file mode 100644 index 000000000..d7036ebc2 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinmetadata.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinmetadata.h" + +#include <gst/gst.h> +#include <gst/gstversion.h> + +struct QGstreamerMetaDataKeyLookup +{ + QtMultimediaKit::MetaData key; + const char *token; +}; + +static const QGstreamerMetaDataKeyLookup qt_gstreamerMetaDataKeys[] = +{ + { QtMultimediaKit::Title, GST_TAG_TITLE }, + //{ QtMultimediaKit::SubTitle, 0 }, + //{ QtMultimediaKit::Author, 0 }, + { QtMultimediaKit::Comment, GST_TAG_COMMENT }, + { QtMultimediaKit::Description, GST_TAG_DESCRIPTION }, + //{ QtMultimediaKit::Category, 0 }, + { QtMultimediaKit::Genre, GST_TAG_GENRE }, + //{ QtMultimediaKit::Year, 0 }, + //{ QtMultimediaKit::UserRating, 0 }, + + { QtMultimediaKit::Language, GST_TAG_LANGUAGE_CODE }, + + { QtMultimediaKit::Publisher, GST_TAG_ORGANIZATION }, + { QtMultimediaKit::Copyright, GST_TAG_COPYRIGHT }, + //{ QtMultimediaKit::ParentalRating, 0 }, + //{ QtMultimediaKit::RatingOrganisation, 0 }, + + // Media + //{ QtMultimediaKit::Size, 0 }, + //{ QtMultimediaKit::MediaType, 0 }, + { QtMultimediaKit::Duration, GST_TAG_DURATION }, + + // Audio + { QtMultimediaKit::AudioBitRate, GST_TAG_BITRATE }, + { QtMultimediaKit::AudioCodec, GST_TAG_AUDIO_CODEC }, + //{ QtMultimediaKit::ChannelCount, 0 }, + //{ QtMultimediaKit::SampleRate, 0 }, + + // Music + { QtMultimediaKit::AlbumTitle, GST_TAG_ALBUM }, + { QtMultimediaKit::AlbumArtist, GST_TAG_ARTIST}, + { QtMultimediaKit::ContributingArtist, GST_TAG_PERFORMER }, +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + { QtMultimediaKit::Composer, GST_TAG_COMPOSER }, +#endif + //{ QtMultimediaKit::Conductor, 0 }, + //{ QtMultimediaKit::Lyrics, 0 }, + //{ QtMultimediaKit::Mood, 0 }, + { QtMultimediaKit::TrackNumber, GST_TAG_TRACK_NUMBER }, + + //{ QtMultimediaKit::CoverArtUrlSmall, 0 }, + //{ QtMultimediaKit::CoverArtUrlLarge, 0 }, + + // Image/Video + //{ QtMultimediaKit::Resolution, 0 }, + //{ QtMultimediaKit::PixelAspectRatio, 0 }, + + // Video + //{ QtMultimediaKit::VideoFrameRate, 0 }, + //{ QtMultimediaKit::VideoBitRate, 0 }, + { QtMultimediaKit::VideoCodec, GST_TAG_VIDEO_CODEC }, + + //{ QtMultimediaKit::PosterUrl, 0 }, + + // Movie + //{ QtMultimediaKit::ChapterNumber, 0 }, + //{ QtMultimediaKit::Director, 0 }, + { QtMultimediaKit::LeadPerformer, GST_TAG_PERFORMER }, + //{ QtMultimediaKit::Writer, 0 }, + + // Photos + //{ QtMultimediaKit::CameraManufacturer, 0 }, + //{ QtMultimediaKit::CameraModel, 0 }, + //{ QtMultimediaKit::Event, 0 }, + //{ QtMultimediaKit::Subject, 0 } +}; + +CameraBinMetaData::CameraBinMetaData(QObject *parent) + :QMetaDataWriterControl(parent) +{ +} + +QVariant CameraBinMetaData::metaData(QtMultimediaKit::MetaData key) const +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + const char *name = qt_gstreamerMetaDataKeys[i].token; + + return m_values.value(QByteArray::fromRawData(name, qstrlen(name))); + } + } + return QVariant(); +} + +void CameraBinMetaData::setMetaData(QtMultimediaKit::MetaData key, const QVariant &value) +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + const char *name = qt_gstreamerMetaDataKeys[i].token; + + m_values.insert(QByteArray::fromRawData(name, qstrlen(name)), value); + + emit QMetaDataWriterControl::metaDataChanged(); + emit metaDataChanged(m_values); + + return; + } + } +} + +QList<QtMultimediaKit::MetaData> CameraBinMetaData::availableMetaData() const +{ + static QMap<QByteArray, QtMultimediaKit::MetaData> keysMap; + if (keysMap.isEmpty()) { + const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + for (int i = 0; i < count; ++i) { + keysMap[QByteArray(qt_gstreamerMetaDataKeys[i].token)] = qt_gstreamerMetaDataKeys[i].key; + } + } + + QList<QtMultimediaKit::MetaData> res; + foreach (const QByteArray &key, m_values.keys()) { + QtMultimediaKit::MetaData tag = keysMap.value(key, QtMultimediaKit::MetaData(-1)); + if (tag != -1) + res.append(tag); + } + + return res; +} + +QVariant CameraBinMetaData::extendedMetaData(QString const &name) const +{ + return m_values.value(name.toLatin1()); +} + +void CameraBinMetaData::setExtendedMetaData(QString const &name, QVariant const &value) +{ + m_values.insert(name.toLatin1(), value); + emit QMetaDataWriterControl::metaDataChanged(); + emit metaDataChanged(m_values); +} + +QStringList CameraBinMetaData::availableExtendedMetaData() const +{ + QStringList res; + foreach (const QByteArray &key, m_values.keys()) + res.append(QString(key)); + + return res; +} diff --git a/src/plugins/gstreamer/camerabin/camerabinmetadata.h b/src/plugins/gstreamer/camerabin/camerabinmetadata.h new file mode 100644 index 000000000..be00da839 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinmetadata.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINCAPTUREMETADATACONTROL_H +#define CAMERABINCAPTUREMETADATACONTROL_H + +#include <qmetadatawritercontrol.h> + +QT_USE_NAMESPACE + +class CameraBinMetaData : public QMetaDataWriterControl +{ + Q_OBJECT +public: + CameraBinMetaData(QObject *parent); + virtual ~CameraBinMetaData() {} + + + bool isMetaDataAvailable() const { return true; } + bool isWritable() const { return true; } + + QVariant metaData(QtMultimediaKit::MetaData key) const; + void setMetaData(QtMultimediaKit::MetaData key, const QVariant &value); + QList<QtMultimediaKit::MetaData> availableMetaData() const; + + QVariant extendedMetaData(QString const &name) const; + void setExtendedMetaData(QString const &name, QVariant const &value); + QStringList availableExtendedMetaData() const; + +Q_SIGNALS: + void metaDataChanged(const QMap<QByteArray, QVariant>&); + +private: + QMap<QByteArray, QVariant> m_values; +}; + +#endif // CAMERABINCAPTUREMETADATACONTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp b/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp new file mode 100644 index 000000000..0cd8a5aaf --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinrecorder.cpp @@ -0,0 +1,225 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinrecorder.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabincontainer.h" +#include <QtCore/QDebug> + +CameraBinRecorder::CameraBinRecorder(CameraBinSession *session) + :QMediaRecorderControl(session), + m_session(session), + m_state(QMediaRecorder::StoppedState) +{ + connect(m_session, SIGNAL(stateChanged(QCamera::State)), SLOT(updateState())); + connect(m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedChanged(bool)), this, SIGNAL(mutedChanged(bool))); +} + +CameraBinRecorder::~CameraBinRecorder() +{ +} + +QUrl CameraBinRecorder::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool CameraBinRecorder::setOutputLocation(const QUrl &sink) +{ + m_session->setOutputLocation(sink); + return true; +} + +QMediaRecorder::State CameraBinRecorder::state() const +{ + return m_state; +} + +void CameraBinRecorder::updateState() +{ + if (m_session->state() != QCamera::ActiveState && + m_state != QMediaRecorder::StoppedState) { + m_session->stopVideoRecording(); + emit stateChanged(m_state = QMediaRecorder::StoppedState); + } +} + +qint64 CameraBinRecorder::duration() const +{ + return m_session->duration(); +} + +void CameraBinRecorder::record() +{ + if (m_session->state() == QCamera::ActiveState) { + if (m_state == QMediaRecorder::PausedState) + m_session->resumeVideoRecording(); + else + m_session->recordVideo(); + emit stateChanged(m_state = QMediaRecorder::RecordingState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); +} + +void CameraBinRecorder::pause() +{ + if (m_session->state() == QCamera::ActiveState) { + m_session->pauseVideoRecording(); + emit stateChanged(m_state = QMediaRecorder::PausedState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); +} + +void CameraBinRecorder::stop() +{ + if (m_session->state() == QCamera::ActiveState) { + m_session->stopVideoRecording(); + emit stateChanged(m_state = QMediaRecorder::StoppedState); + } +} + +bool CameraBinRecorder::findCodecs() +{ + //Check the codecs are compatible with container, + //and choose the compatible codecs/container if omitted + CameraBinAudioEncoder *audioEncodeControl = m_session->audioEncodeControl(); + CameraBinVideoEncoder *videoEncodeControl = m_session->videoEncodeControl(); + CameraBinContainer *mediaContainerControl = m_session->mediaContainerControl(); + + audioEncodeControl->resetActualSettings(); + videoEncodeControl->resetActualSettings(); + mediaContainerControl->resetActualContainer(); + + QStringList containerCandidates; + if (mediaContainerControl->containerMimeType().isEmpty()) + containerCandidates = mediaContainerControl->supportedContainers(); + else + containerCandidates << mediaContainerControl->containerMimeType(); + + + QStringList audioCandidates; + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + if (audioSettings.codec().isEmpty()) + audioCandidates = audioEncodeControl->supportedAudioCodecs(); + else + audioCandidates << audioSettings.codec(); + + QStringList videoCandidates; + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + if (videoSettings.codec().isEmpty()) + videoCandidates = videoEncodeControl->supportedVideoCodecs(); + else + videoCandidates << videoSettings.codec(); + + QString container; + QString audioCodec; + QString videoCodec; + + foreach (const QString &containerCandidate, containerCandidates) { + QSet<QString> supportedTypes = mediaContainerControl->supportedStreamTypes(containerCandidate); + + audioCodec.clear(); + videoCodec.clear(); + + bool found = false; + foreach (const QString &audioCandidate, audioCandidates) { + QSet<QString> audioTypes = audioEncodeControl->supportedStreamTypes(audioCandidate); + if (!audioTypes.intersect(supportedTypes).isEmpty()) { + found = true; + audioCodec = audioCandidate; + break; + } + } + if (!found) + continue; + + found = false; + foreach (const QString &videoCandidate, videoCandidates) { + QSet<QString> videoTypes = videoEncodeControl->supportedStreamTypes(videoCandidate); + if (!videoTypes.intersect(supportedTypes).isEmpty()) { + found = true; + videoCodec = videoCandidate; + break; + } + } + if (!found) + continue; + + + container = containerCandidate; + break; + } + + if (container.isEmpty()) { + qWarning() << "Camera error: Not compatible codecs and container format."; + emit error(QMediaRecorder::FormatError, tr("Not compatible codecs and container format.")); + return false; + } else { + mediaContainerControl->setActualContainer(container); + + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + audioSettings.setCodec(audioCodec); + audioEncodeControl->setActualAudioSettings(audioSettings); + + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + videoSettings.setCodec(videoCodec); + videoEncodeControl->setActualVideoSettings(videoSettings); + } + + return true; +} + +void CameraBinRecorder::applySettings() +{ + findCodecs(); +} + +bool CameraBinRecorder::isMuted() const +{ + return m_session->isMuted(); +} + +void CameraBinRecorder::setMuted(bool muted) +{ + m_session->setMuted(muted); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinrecorder.h b/src/plugins/gstreamer/camerabin/camerabinrecorder.h new file mode 100644 index 000000000..a6faf9b64 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinrecorder.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABINRECORDERCONTROL_H +#define CAMERABINRECORDERCONTROL_H + +#include <qmediarecordercontrol.h> +#include "camerabinsession.h" +QT_USE_NAMESPACE + +class CameraBinRecorder : public QMediaRecorderControl +{ + Q_OBJECT + +public: + CameraBinRecorder(CameraBinSession *session); + virtual ~CameraBinRecorder(); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl &sink); + + QMediaRecorder::State state() const; + + qint64 duration() const; + + bool isMuted() const; + + bool findCodecs(); + + void applySettings(); + +public slots: + void record(); + void pause(); + void stop(); + void setMuted(bool); + +private slots: + void updateState(); + +private: + CameraBinSession *m_session; + QMediaRecorder::State m_state; +}; + +#endif // CAMERABINCAPTURECORNTROL_H diff --git a/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.cpp b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.cpp new file mode 100644 index 000000000..db9218c4a --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.cpp @@ -0,0 +1,188 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinresourcepolicy.h" + +#ifdef Q_WS_MAEMO_6 +#define HAVE_RESOURCE_POLICY +#endif + +//#define DEBUG_RESOURCE_POLICY +#include <QtCore/qdebug.h> +#include <QtCore/qset.h> + +#ifdef HAVE_RESOURCE_POLICY +#include <policy/resource.h> +#include <policy/resources.h> +#include <policy/resource-set.h> +#endif + +CamerabinResourcePolicy::CamerabinResourcePolicy(QObject *parent) : + QObject(parent), + m_resourceSet(NoResources), + m_releasingResources(false) +{ +#ifdef HAVE_RESOURCE_POLICY + //loaded resource set is also kept requested for image and video capture sets + m_resource = new ResourcePolicy::ResourceSet("camera"); + m_resource->setAlwaysReply(); + m_resource->initAndConnect(); + + connect(m_resource, SIGNAL(resourcesGranted(const QList<ResourcePolicy::ResourceType>)), + SIGNAL(resourcesGranted())); + connect(m_resource, SIGNAL(resourcesDenied()), SIGNAL(resourcesDenied())); + connect(m_resource, SIGNAL(lostResources()), SIGNAL(resourcesLost())); + connect(m_resource, SIGNAL(resourcesReleased()), SLOT(handleResourcesReleased())); +#endif +} + +CamerabinResourcePolicy::~CamerabinResourcePolicy() +{ +#ifdef HAVE_RESOURCE_POLICY + //ensure the resources are released + if (m_resourceSet != NoResources) + setResourceSet(NoResources); + + //don't delete the resource set until resources are released + if (m_releasingResources) { + m_resource->connect(m_resource, SIGNAL(resourcesReleased()), + SLOT(deleteLater())); + } else { + delete m_resource; + m_resource = 0; + } +#endif +} + +CamerabinResourcePolicy::ResourceSet CamerabinResourcePolicy::resourceSet() const +{ + return m_resourceSet; +} + +void CamerabinResourcePolicy::setResourceSet(CamerabinResourcePolicy::ResourceSet set) +{ + CamerabinResourcePolicy::ResourceSet oldSet = m_resourceSet; + m_resourceSet = set; + +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << set; +#endif + +#ifdef HAVE_RESOURCE_POLICY + QSet<ResourcePolicy::ResourceType> requestedTypes; + + switch (set) { + case NoResources: + break; + case LoadedResources: + requestedTypes << ResourcePolicy::LensCoverType //to detect lens cover is opened/closed + << ResourcePolicy::VideoRecorderType //to open camera device + << ResourcePolicy::SnapButtonType; //to detect capture button events + break; + case ImageCaptureResources: + requestedTypes << ResourcePolicy::LensCoverType + << ResourcePolicy::VideoPlaybackType + << ResourcePolicy::VideoRecorderType + << ResourcePolicy::AudioPlaybackType + << ResourcePolicy::ScaleButtonType + << ResourcePolicy::LedsType + << ResourcePolicy::SnapButtonType; + break; + case VideoCaptureResources: + requestedTypes << ResourcePolicy::LensCoverType + << ResourcePolicy::VideoPlaybackType + << ResourcePolicy::VideoRecorderType + << ResourcePolicy::AudioPlaybackType + << ResourcePolicy::AudioRecorderType + << ResourcePolicy::ScaleButtonType + << ResourcePolicy::LedsType + << ResourcePolicy::SnapButtonType; + break; + } + + QSet<ResourcePolicy::ResourceType> currentTypes; + foreach (ResourcePolicy::Resource *resource, m_resource->resources()) + currentTypes << resource->type(); + + foreach (ResourcePolicy::ResourceType resourceType, currentTypes - requestedTypes) + m_resource->deleteResource(resourceType); + + foreach (ResourcePolicy::ResourceType resourceType, requestedTypes - currentTypes) { + if (resourceType == ResourcePolicy::LensCoverType) { + ResourcePolicy::LensCoverResource *lensCoverResource = new ResourcePolicy::LensCoverResource; + lensCoverResource->setOptional(true); + m_resource->addResourceObject(lensCoverResource); + } else { + m_resource->addResource(resourceType); + } + } + + m_resource->update(); + if (set != NoResources) { + m_resource->acquire(); + } else { + if (oldSet != NoResources) { + m_releasingResources = true; + m_resource->release(); + } + } +#endif +} + +bool CamerabinResourcePolicy::isResourcesGranted() const +{ +#ifdef HAVE_RESOURCE_POLICY + foreach (ResourcePolicy::Resource *resource, m_resource->resources()) + if (!resource->isOptional() && !resource->isGranted()) + return false; +#endif + return true; +} + +void CamerabinResourcePolicy::handleResourcesReleased() +{ +#ifdef HAVE_RESOURCE_POLICY +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO; +#endif + m_releasingResources = false; +#endif +} diff --git a/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h new file mode 100644 index 000000000..cd2d84688 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinresourcepolicy.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERARESOURCEPOLICY_H +#define CAMERARESOURCEPOLICY_H + +#include <QtCore/qobject.h> + +namespace ResourcePolicy { +class ResourceSet; +}; + +class CamerabinResourcePolicy : public QObject +{ + Q_OBJECT +public: + enum ResourceSet { + NoResources, + LoadedResources, + ImageCaptureResources, + VideoCaptureResources + }; + + CamerabinResourcePolicy(QObject *parent); + ~CamerabinResourcePolicy(); + + ResourceSet resourceSet() const; + void setResourceSet(ResourceSet set); + + bool isResourcesGranted() const; + +Q_SIGNALS: + void resourcesDenied(); + void resourcesGranted(); + void resourcesLost(); + +private Q_SLOTS: + void handleResourcesReleased(); + +private: + ResourceSet m_resourceSet; + ResourcePolicy::ResourceSet *m_resource; + bool m_releasingResources; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabin/camerabinservice.cpp b/src/plugins/gstreamer/camerabin/camerabinservice.cpp new file mode 100644 index 000000000..261a9c308 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinservice.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinservice.h" +#include "camerabinsession.h" +#include "camerabinrecorder.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" +#include "qgstreamerbushelper.h" +#include "camerabincontrol.h" +#include "camerabinlocks.h" +#include "camerabinmetadata.h" +#include "camerabinexposure.h" +#include "camerabinflash.h" +#include "camerabinfocus.h" +#include "camerabinimagecapture.h" +#include "camerabinimageprocessing.h" +#include "camerabincapturebufferformat.h" +#include "camerabincapturedestination.h" + +#include "qgstreameraudioinputendpointselector.h" +#include "qgstreamervideoinputdevicecontrol.h" + +#include "qgstreamervideooverlay.h" +#include "qgstreamervideowindow.h" +#include "qgstreamervideorenderer.h" + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) +#include "qgstreamergltexturerenderer.h" +#endif + +#include "qgstreamervideowidget.h" + +#include <qmediaserviceprovider.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qprocess.h> + +#if defined(Q_WS_MAEMO_5) +#include "camerabuttonlistener_maemo.h" +#endif + +#if defined(Q_WS_MAEMO_6) +#include "camerabuttonlistener_meego.h" +#endif + +CameraBinService::CameraBinService(const QString &service, QObject *parent): + QMediaService(parent) +{ + m_captureSession = 0; + m_cameraControl = 0; + m_metaDataControl = 0; + + m_audioInputEndpointSelector = 0; + m_videoInputDevice = 0; + + m_videoOutput = 0; + m_videoRenderer = 0; + m_videoWindow = 0; + m_videoWidgetControl = 0; + m_imageCaptureControl = 0; + + if (service == Q_MEDIASERVICE_CAMERA) { + m_captureSession = new CameraBinSession(this); + m_cameraControl = new CameraBinControl(m_captureSession); + m_videoInputDevice = new QGstreamerVideoInputDeviceControl(m_captureSession); + m_imageCaptureControl = new CameraBinImageCapture(m_captureSession); + + connect(m_videoInputDevice, SIGNAL(selectedDeviceChanged(QString)), + m_captureSession, SLOT(setDevice(QString))); + + if (m_videoInputDevice->deviceCount()) + m_captureSession->setDevice(m_videoInputDevice->deviceName(m_videoInputDevice->selectedDevice())); + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) + m_videoRenderer = new QGstreamerGLTextureRenderer(this); +#else + m_videoRenderer = new QGstreamerVideoRenderer(this); +#endif + + +#ifdef Q_WS_MAEMO_6 + m_videoWindow = new QGstreamerVideoWindow(this, "omapxvsink"); + //m_videoWindow = new QGstreamerVideoWindow(this); +#else + m_videoWindow = new QGstreamerVideoOverlay(this); +#endif + + m_videoWidgetControl = new QGstreamerVideoWidgetControl(this); + + } + + if (!m_captureSession) { + qWarning() << Q_FUNC_INFO << "Service type is not supported:" << service; + return; + } + + m_audioInputEndpointSelector = new QGstreamerAudioInputEndpointSelector(this); + connect(m_audioInputEndpointSelector, SIGNAL(activeEndpointChanged(QString)), m_captureSession, SLOT(setCaptureDevice(QString))); + + if (m_captureSession && m_audioInputEndpointSelector->availableEndpoints().size() > 0) + m_captureSession->setCaptureDevice(m_audioInputEndpointSelector->defaultEndpoint()); + + m_metaDataControl = new CameraBinMetaData(this); + connect(m_metaDataControl, SIGNAL(metaDataChanged(QMap<QByteArray,QVariant>)), + m_captureSession, SLOT(setMetaData(QMap<QByteArray,QVariant>))); + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + new CameraButtonListener(this); +#endif + +#if defined(Q_WS_MAEMO_5) + //disable the system camera application + QProcess::execute("/usr/sbin/dsmetool -k /usr/bin/camera-ui"); +#endif +} + +CameraBinService::~CameraBinService() +{ +#if defined(Q_WS_MAEMO_5) + //restore the system camera application + QProcess::execute("/usr/sbin/dsmetool -U user -o /usr/bin/camera-ui"); +#endif +} + +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; + m_captureSession->setViewfinder(m_videoRenderer); + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + m_videoOutput = m_videoWindow; + m_captureSession->setViewfinder(m_videoWindow); + } else if (qstrcmp(name, QVideoWidgetControl_iid) == 0) { + m_captureSession->setViewfinder(m_videoWidgetControl); + m_videoOutput = m_videoWidgetControl; + } + + if (m_videoOutput) + return m_videoOutput; + } + + if (qstrcmp(name,QAudioEndpointSelector_iid) == 0) + return m_audioInputEndpointSelector; + + if (qstrcmp(name,QVideoDeviceControl_iid) == 0) + return m_videoInputDevice; + + if (qstrcmp(name,QMediaRecorderControl_iid) == 0) + return m_captureSession->recorderControl(); + + if (qstrcmp(name,QAudioEncoderControl_iid) == 0) + return m_captureSession->audioEncodeControl(); + + if (qstrcmp(name,QVideoEncoderControl_iid) == 0) + return m_captureSession->videoEncodeControl(); + + if (qstrcmp(name,QImageEncoderControl_iid) == 0) + return m_captureSession->imageEncodeControl(); + + + if (qstrcmp(name,QMediaContainerControl_iid) == 0) + return m_captureSession->mediaContainerControl(); + + if (qstrcmp(name,QCameraControl_iid) == 0) + return m_cameraControl; + + if (qstrcmp(name,QMetaDataWriterControl_iid) == 0) + return m_metaDataControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + + if (qstrcmp(name, QCameraExposureControl_iid) == 0) + return m_captureSession->cameraExposureControl(); + + if (qstrcmp(name, QCameraFlashControl_iid) == 0) + return m_captureSession->cameraFlashControl(); + + if (qstrcmp(name, QCameraFocusControl_iid) == 0) + return m_captureSession->cameraFocusControl(); + + if (qstrcmp(name, QCameraImageProcessingControl_iid) == 0) + return m_captureSession->imageProcessingControl(); + + if (qstrcmp(name, QCameraLocksControl_iid) == 0) + return m_captureSession->cameraLocksControl(); + + if (qstrcmp(name, QCameraCaptureDestinationControl_iid) == 0) + return m_captureSession->captureDestinationControl(); + + if (qstrcmp(name, QCameraCaptureBufferFormatControl_iid) == 0) + return m_captureSession->captureBufferFormatControl(); + + return 0; +} + +void CameraBinService::releaseControl(QMediaControl *control) +{ + if (control && control == m_videoOutput) { + m_videoOutput = 0; + m_captureSession->setViewfinder(0); + } +} + +bool CameraBinService::isCameraBinAvailable() +{ + GstElementFactory *factory = gst_element_factory_find("camerabin"); + if (factory) { + gst_object_unref(GST_OBJECT(factory)); + return true; + } + + return false; +} diff --git a/src/plugins/gstreamer/camerabin/camerabinservice.h b/src/plugins/gstreamer/camerabin/camerabinservice.h new file mode 100644 index 000000000..11c5aeadf --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinservice.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINCAPTURESERVICE_H +#define CAMERABINCAPTURESERVICE_H + +#include <qmediaservice.h> + +#include <gst/gst.h> +QT_BEGIN_NAMESPACE +class QAudioEndpointSelector; +class QVideoDeviceControl; +QT_END_NAMESPACE + +class CameraBinSession; +class CameraBinControl; +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerVideoRenderer; +class QGstreamerVideoOverlay; +class QGstreamerVideoWidgetControl; +class QGstreamerElementFactory; +class CameraBinMetaData; +class CameraBinImageCapture; +class CameraBinMetaData; + +class CameraBinService : public QMediaService +{ + Q_OBJECT + +public: + CameraBinService(const QString &service, QObject *parent = 0); + virtual ~CameraBinService(); + + QMediaControl *requestControl(const char *name); + void releaseControl(QMediaControl *); + + static bool isCameraBinAvailable(); + +private: + void setAudioPreview(GstElement*); + + CameraBinSession *m_captureSession; + CameraBinControl *m_cameraControl; + CameraBinMetaData *m_metaDataControl; + + QAudioEndpointSelector *m_audioInputEndpointSelector; + QVideoDeviceControl *m_videoInputDevice; + + QMediaControl *m_videoOutput; + + QMediaControl *m_videoRenderer; + QMediaControl *m_videoWindow; + QGstreamerVideoWidgetControl *m_videoWidgetControl; + CameraBinImageCapture *m_imageCaptureControl; +}; + +#endif // CAMERABINCAPTURESERVICE_H diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.cpp b/src/plugins/gstreamer/camerabin/camerabinsession.cpp new file mode 100644 index 000000000..e84a5af24 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinsession.cpp @@ -0,0 +1,1267 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "camerabinsession.h" +#include "camerabinrecorder.h" +#include "camerabincontainer.h" +#include "camerabinaudioencoder.h" +#include "camerabinvideoencoder.h" +#include "camerabinimageencoder.h" +#include "camerabinexposure.h" +#include "camerabinflash.h" +#include "camerabinfocus.h" +#include "camerabinimageprocessing.h" +#include "camerabinlocks.h" +#include "camerabincapturedestination.h" +#include "camerabincapturebufferformat.h" +#include "qgstreamerbushelper.h" +#include "qgstreamervideorendererinterface.h" +#include <qmediarecorder.h> +#include <gst/interfaces/photography.h> +#include <gst/gsttagsetter.h> +#include <gst/gstversion.h> + +#include <QtCore/qdebug.h> +#include <QCoreApplication> +#include <QtCore/qmetaobject.h> +#include <QtGui/qdesktopservices.h> + +#include <QtGui/qimage.h> + +//#define CAMERABIN_DEBUG 1 +#define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + +#ifdef Q_WS_MAEMO_5 +#define FILENAME_PROPERTY "filename" +#define MODE_PROPERTY "mode" +#define MUTE_PROPERTY "mute" +#define ZOOM_PROPERTY "zoom" +#define IMAGE_PP_PROPERTY "imagepp" +#define IMAGE_ENCODER_PROPERTY "imageenc" +#define VIDEO_PP_PROPERTY "videopp" +#define VIDEO_ENCODER_PROPERTY "videoenc" +#define AUDIO_ENCODER_PROPERTY "audioenc" +#define VIDEO_MUXER_PROPERTY "videomux" +#define VIEWFINDER_SINK_PROPERTY "vfsink" +#define VIDEO_SOURCE_PROPERTY "videosrc" +#define AUDIO_SOURCE_PROPERTY "audiosrc" +#define VIDEO_SOURCE_CAPS_PROPERTY "inputcaps" +#define FILTER_CAPS_PROPERTY "filter-caps" +#define PREVIEW_CAPS_PROPERTY "preview-caps" + +#define IMAGE_DONE_SIGNAL "img-done" +#define CAPTURE_START "user-start" +#define CAPTURE_STOP "user-stop" +#define CAPTURE_PAUSE "user-pause" +#define SET_VIDEO_RESOLUTION_FPS "user-res-fps" +#define SET_IMAGE_RESOLUTION "user-image-res" + +#else + +#define FILENAME_PROPERTY "filename" +#define MODE_PROPERTY "mode" +#define MUTE_PROPERTY "mute" +#define ZOOM_PROPERTY "zoom" +#define IMAGE_PP_PROPERTY "image-post-processing" +#define IMAGE_ENCODER_PROPERTY "image-encoder" +#define VIDEO_PP_PROPERTY "video-post-processing" +#define VIDEO_ENCODER_PROPERTY "video-encoder" +#define AUDIO_ENCODER_PROPERTY "audio-encoder" +#define VIDEO_MUXER_PROPERTY "video-muxer" +#define VIEWFINDER_SINK_PROPERTY "viewfinder-sink" +#define VIDEO_SOURCE_PROPERTY "video-source" +#define AUDIO_SOURCE_PROPERTY "audio-source" +#define VIDEO_SOURCE_CAPS_PROPERTY "video-source-caps" +#define FILTER_CAPS_PROPERTY "filter-caps" +#define PREVIEW_CAPS_PROPERTY "preview-caps" + +#define IMAGE_DONE_SIGNAL "image-done" +#define CAPTURE_START "capture-start" +#define CAPTURE_STOP "capture-stop" +#define CAPTURE_PAUSE "capture-pause" +#define SET_VIDEO_RESOLUTION_FPS "set-video-resolution-fps" +#define SET_IMAGE_RESOLUTION "set-image-resolution" +#endif + +#define CAMERABIN_IMAGE_MODE 0 +#define CAMERABIN_VIDEO_MODE 1 + +#define gstRef(element) { gst_object_ref(GST_OBJECT(element)); gst_object_sink(GST_OBJECT(element)); } +#define gstUnref(element) { if (element) { gst_object_unref(GST_OBJECT(element)); element = 0; } } + +#define PREVIEW_CAPS_4_3 \ + "video/x-raw-rgb, width = (int) 640, height = (int) 480" + +#define VIEWFINDER_RESOLUTION_4x3 QSize(640, 480) +#define VIEWFINDER_RESOLUTION_3x2 QSize(720, 480) +#define VIEWFINDER_RESOLUTION_16x9 QSize(800, 450) + +//using GST_STATE_READY for QCamera::LoadedState +//doesn't work reliably at least with some webcams. +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) +#define USE_READY_STATE_ON_LOADED +#endif + +CameraBinSession::CameraBinSession(QObject *parent) + :QObject(parent), + m_state(QCamera::UnloadedState), + m_pendingState(QCamera::UnloadedState), + m_recordingActive(false), + m_pendingResolutionUpdate(false), + m_muted(false), + m_busy(false), + m_captureMode(QCamera::CaptureStillImage), + m_audioInputFactory(0), + m_videoInputFactory(0), + m_viewfinder(0), + m_viewfinderInterface(0), + m_pipeline(0), + m_videoSrc(0), + m_viewfinderElement(0), + m_viewfinderHasChanged(true), + m_videoInputHasChanged(true), + m_sourceCaps(0), + m_audioSrc(0), + m_audioConvert(0), + m_capsFilter(0), + m_fileSink(0), + m_audioEncoder(0), + m_muxer(0) +{ + m_pipeline = gst_element_factory_make("camerabin", "camerabin"); + g_signal_connect(G_OBJECT(m_pipeline), "notify::idle", G_CALLBACK(updateBusyStatus), this); + + gstRef(m_pipeline); + + m_bus = gst_element_get_bus(m_pipeline); + + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installSyncEventFilter(this); + connect(m_busHelper, SIGNAL(message(QGstreamerMessage)), SLOT(handleBusMessage(QGstreamerMessage))); + m_audioEncodeControl = new CameraBinAudioEncoder(this); + m_videoEncodeControl = new CameraBinVideoEncoder(this); + m_imageEncodeControl = new CameraBinImageEncoder(this); + m_recorderControl = new CameraBinRecorder(this); + m_mediaContainerControl = new CameraBinContainer(this); + m_cameraExposureControl = new CameraBinExposure(this); + m_cameraFlashControl = new CameraBinFlash(this); + m_cameraFocusControl = new CameraBinFocus(this); + m_imageProcessingControl = new CameraBinImageProcessing(this); + m_cameraLocksControl = new CameraBinLocks(this); + m_captureDestinationControl = new CameraBinCaptureDestination(this); + m_captureBufferFormatControl = new CameraBinCaptureBufferFormat(this); +} + +CameraBinSession::~CameraBinSession() +{ + if (m_pipeline) { + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_element_get_state(m_pipeline, NULL, NULL, GST_CLOCK_TIME_NONE); + gstUnref(m_pipeline); + gstUnref(m_viewfinderElement); + } +} + +GstPhotography *CameraBinSession::photography() +{ + if (GST_IS_PHOTOGRAPHY(m_pipeline)) { + return GST_PHOTOGRAPHY(m_pipeline); + } + + if (!m_videoSrc) { + m_videoSrc = buildVideoSrc(); + + if (m_videoSrc) + g_object_set(m_pipeline, VIDEO_SOURCE_PROPERTY, m_videoSrc, NULL); + else + g_object_get(m_pipeline, VIDEO_SOURCE_PROPERTY, &m_videoSrc, NULL); + + updateVideoSourceCaps(); + m_videoInputHasChanged = false; + } + + if (m_videoSrc && GST_IS_PHOTOGRAPHY(m_videoSrc)) + return GST_PHOTOGRAPHY(m_videoSrc); + + return 0; +} + +CameraBinSession::CameraRole CameraBinSession::cameraRole() const +{ +#ifdef Q_WS_MAEMO_5 + return m_inputDevice == QLatin1String("/dev/video1") ? + FrontCamera : BackCamera; +#endif + + return BackCamera; +} + +bool CameraBinSession::setupCameraBin() +{ + if (m_captureMode == QCamera::CaptureStillImage) { + g_object_set(m_pipeline, MODE_PROPERTY, CAMERABIN_IMAGE_MODE, NULL); + } + + if (m_captureMode == QCamera::CaptureVideo) { + g_object_set(m_pipeline, MODE_PROPERTY, CAMERABIN_VIDEO_MODE, NULL); + + if (!m_recorderControl->findCodecs()) + return false; + + g_object_set(m_pipeline, VIDEO_ENCODER_PROPERTY, m_videoEncodeControl->createEncoder(), NULL); + g_object_set(m_pipeline, AUDIO_ENCODER_PROPERTY, m_audioEncodeControl->createEncoder(), NULL); + g_object_set(m_pipeline, VIDEO_MUXER_PROPERTY, + gst_element_factory_make(m_mediaContainerControl->formatElementName().constData(), NULL), NULL); + } + + if (m_videoInputHasChanged) { + m_videoSrc = buildVideoSrc(); + + if (m_videoSrc) + g_object_set(m_pipeline, VIDEO_SOURCE_PROPERTY, m_videoSrc, NULL); + else + g_object_get(m_pipeline, VIDEO_SOURCE_PROPERTY, &m_videoSrc, NULL); + + updateVideoSourceCaps(); + m_videoInputHasChanged = false; + } + + + if (m_viewfinderHasChanged) { + if (m_viewfinderElement) + gst_object_unref(GST_OBJECT(m_viewfinderElement)); + + m_viewfinderElement = m_viewfinderInterface ? m_viewfinderInterface->videoSink() : 0; +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "Viewfinder changed, reconfigure."; +#endif + m_viewfinderHasChanged = false; + if (!m_viewfinderElement) { + qWarning() << "Staring camera without viewfinder available"; + m_viewfinderElement = gst_element_factory_make("fakesink", NULL); + } + gst_object_ref(GST_OBJECT(m_viewfinderElement)); + gst_element_set_state(m_pipeline, GST_STATE_NULL); + g_object_set(G_OBJECT(m_pipeline), VIEWFINDER_SINK_PROPERTY, m_viewfinderElement, NULL); + } + + GstCaps *previewCaps = gst_caps_from_string(PREVIEW_CAPS_4_3); + g_object_set(G_OBJECT(m_pipeline), PREVIEW_CAPS_PROPERTY, previewCaps, NULL); + gst_caps_unref(previewCaps); + + return true; +} + +void CameraBinSession::updateVideoSourceCaps() +{ + if (m_sourceCaps) { + gst_caps_unref(m_sourceCaps); + m_sourceCaps = 0; + } + + g_object_get(G_OBJECT(m_pipeline), VIDEO_SOURCE_CAPS_PROPERTY, &m_sourceCaps, NULL); +} + +void CameraBinSession::setupCaptureResolution() +{ + if (m_captureMode == QCamera::CaptureStillImage) { + QSize resolution = m_imageEncodeControl->imageSettings().resolution(); + + //by default select the maximum supported resolution + if (resolution.isEmpty()) { + updateVideoSourceCaps(); + bool continuous = false; + QList<QSize> resolutions = supportedResolutions(qMakePair<int,int>(0,0), + &continuous, + QCamera::CaptureStillImage); + if (!resolutions.isEmpty()) + resolution = resolutions.last(); + } + + QString previewCapsString = PREVIEW_CAPS_4_3; + QSize viewfinderResolution = VIEWFINDER_RESOLUTION_4x3; + + if (!resolution.isEmpty()) { +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "set image resolution" << resolution; +#endif + g_signal_emit_by_name(G_OBJECT(m_pipeline), SET_IMAGE_RESOLUTION, resolution.width(), resolution.height(), NULL); + + previewCapsString = QString("video/x-raw-rgb, width = (int) %1, height = (int) 480") + .arg(resolution.width()*480/resolution.height()); + + if (!resolution.isEmpty()) { + qreal aspectRatio = qreal(resolution.width()) / resolution.height(); + if (aspectRatio < 1.4) + viewfinderResolution = VIEWFINDER_RESOLUTION_4x3; + else if (aspectRatio > 1.7) + viewfinderResolution = VIEWFINDER_RESOLUTION_16x9; + else + viewfinderResolution = VIEWFINDER_RESOLUTION_3x2; + } + } + + GstCaps *previewCaps = gst_caps_from_string(previewCapsString.toLatin1()); + g_object_set(G_OBJECT(m_pipeline), PREVIEW_CAPS_PROPERTY, previewCaps, NULL); + gst_caps_unref(previewCaps); + + //on low res cameras the viewfinder resolution should not be bigger + //then capture resolution + if (viewfinderResolution.width() > resolution.width()) + viewfinderResolution = resolution; + +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "set viewfinder resolution" << viewfinderResolution; +#endif + g_signal_emit_by_name(G_OBJECT(m_pipeline), + SET_VIDEO_RESOLUTION_FPS, + viewfinderResolution.width(), + viewfinderResolution.height(), + 0, // maximum framerate + 1, // framerate denom + NULL); + } + + if (m_captureMode == QCamera::CaptureVideo) { + QSize resolution = m_videoEncodeControl->videoSettings().resolution(); + qreal framerate = m_videoEncodeControl->videoSettings().frameRate(); + + if (resolution.isEmpty()) { + //select the hightest supported resolution + + updateVideoSourceCaps(); + bool continuous = false; + QList<QSize> resolutions = supportedResolutions(qMakePair<int,int>(0,0), + &continuous, + QCamera::CaptureVideo); + if (!resolutions.isEmpty()) + resolution = resolutions.last(); + } + + if (!resolution.isEmpty() || framerate > 0) { +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << "set video resolution" << resolution; +#endif + g_signal_emit_by_name(G_OBJECT(m_pipeline), + SET_VIDEO_RESOLUTION_FPS, + resolution.width(), + resolution.height(), + 0, //framerate nom == max rate + 1, // framerate denom == max rate + NULL); + } + } +} + +GstElement *CameraBinSession::buildVideoSrc() +{ + GstElement *videoSrc = 0; + if (m_videoInputFactory) { + videoSrc = m_videoInputFactory->buildElement(); + } else { + QList<QByteArray> candidates; + candidates << "subdevsrc" + << "v4l2camsrc" + << "v4l2src" + << "autovideosrc"; + QByteArray sourceElementName; + + foreach(sourceElementName, candidates) { + videoSrc = gst_element_factory_make(sourceElementName.constData(), "camera_source"); + if (videoSrc) + break; + } + + if (videoSrc && !m_inputDevice.isEmpty()) { +#if CAMERABIN_DEBUG + qDebug() << "set camera device" << m_inputDevice; +#endif + if (sourceElementName == "subdevsrc") { + if (m_inputDevice == QLatin1String("secondary")) + g_object_set(G_OBJECT(videoSrc), "camera-device", 1, NULL); + else + g_object_set(G_OBJECT(videoSrc), "camera-device", 0, NULL); + } else { + g_object_set(G_OBJECT(videoSrc), "device", m_inputDevice.toLocal8Bit().constData(), NULL); + } + } + } + + return videoSrc; +} + +void CameraBinSession::captureImage(int requestId, const QString &fileName) +{ + QString actualFileName = fileName; + if (actualFileName.isEmpty()) + actualFileName = generateFileName("img_", defaultDir(QCamera::CaptureStillImage), "jpg"); + + m_requestId = requestId; + + g_object_set(G_OBJECT(m_pipeline), FILENAME_PROPERTY, actualFileName.toLocal8Bit().constData(), NULL); + + g_signal_emit_by_name(G_OBJECT(m_pipeline), CAPTURE_START, NULL); + + m_imageFileName = actualFileName; +} + +void CameraBinSession::setCaptureMode(QCamera::CaptureMode mode) +{ + m_captureMode = mode; + + switch (m_captureMode) { + case QCamera::CaptureStillImage: + g_object_set(m_pipeline, MODE_PROPERTY, CAMERABIN_IMAGE_MODE, NULL); + break; + case QCamera::CaptureVideo: + g_object_set(m_pipeline, MODE_PROPERTY, CAMERABIN_VIDEO_MODE, NULL); + break; + } +} + +QUrl CameraBinSession::outputLocation() const +{ + //return the location service wrote data to, not one set by user, it can be empty. + return m_actualSink; +} + +bool CameraBinSession::setOutputLocation(const QUrl& sink) +{ + m_sink = m_actualSink = sink; + return true; +} + +QDir CameraBinSession::defaultDir(QCamera::CaptureMode mode) const +{ + QStringList dirCandidates; + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + dirCandidates << QLatin1String("/home/user/MyDocs/DCIM"); + dirCandidates << QLatin1String("/home/user/MyDocs/"); +#endif + + if (mode == QCamera::CaptureVideo) { + dirCandidates << QDesktopServices::storageLocation(QDesktopServices::MoviesLocation); + dirCandidates << QDir::home().filePath("Documents/Video"); + dirCandidates << QDir::home().filePath("Documents/Videos"); + } else { + dirCandidates << QDesktopServices::storageLocation(QDesktopServices::PicturesLocation); + dirCandidates << QDir::home().filePath("Documents/Photo"); + dirCandidates << QDir::home().filePath("Documents/Photos"); + dirCandidates << QDir::home().filePath("Documents/photo"); + dirCandidates << QDir::home().filePath("Documents/photos"); + dirCandidates << QDir::home().filePath("Documents/Images"); + } + + dirCandidates << QDir::home().filePath("Documents"); + dirCandidates << QDir::home().filePath("My Documents"); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + foreach (const QString &path, dirCandidates) { + if (QFileInfo(path).isWritable()) + return QDir(path); + } + + return QDir(); +} + +QString CameraBinSession::generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const +{ + int lastClip = 0; + foreach(QString fileName, dir.entryList(QStringList() << QString("%1*.%2").arg(prefix).arg(ext))) { + int imgNumber = fileName.mid(prefix.length(), fileName.size()-prefix.length()-ext.length()-1).toInt(); + lastClip = qMax(lastClip, imgNumber); + } + + QString name = QString("%1%2.%3").arg(prefix) + .arg(lastClip+1, + 4, //fieldWidth + 10, + QLatin1Char('0')) + .arg(ext); + + return dir.absoluteFilePath(name); +} + +void CameraBinSession::setDevice(const QString &device) +{ + if (m_inputDevice != device) { + m_inputDevice = device; + m_videoInputHasChanged = true; + } +} + +void CameraBinSession::setAudioInput(QGstreamerElementFactory *audioInput) +{ + m_audioInputFactory = audioInput; +} + +void CameraBinSession::setVideoInput(QGstreamerElementFactory *videoInput) +{ + m_videoInputFactory = videoInput; + m_videoInputHasChanged = true; +} + +bool CameraBinSession::isReady() const +{ + //it's possible to use QCamera without any viewfinder attached + return !m_viewfinderInterface || m_viewfinderInterface->isReady(); +} + +void CameraBinSession::setViewfinder(QObject *viewfinder) +{ + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + m_viewfinderInterface = qobject_cast<QGstreamerVideoRendererInterface*>(viewfinder); + if (!m_viewfinderInterface) + viewfinder = 0; + + if (m_viewfinder != viewfinder) { + bool oldReady = isReady(); + + if (m_viewfinder) { + disconnect(m_viewfinder, SIGNAL(sinkChanged()), + this, SLOT(handleViewfinderChange())); + disconnect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + } + + m_viewfinder = viewfinder; + m_viewfinderHasChanged = true; + + if (m_viewfinder) { + connect(m_viewfinder, SIGNAL(sinkChanged()), + this, SLOT(handleViewfinderChange())); + connect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + } + + emit viewfinderChanged(); + if (oldReady != isReady()) + emit readyChanged(isReady()); + } +} + +void CameraBinSession::handleViewfinderChange() +{ + //the viewfinder will be reloaded + //shortly when the pipeline is started + m_viewfinderHasChanged = true; + emit viewfinderChanged(); +} + +QCamera::State CameraBinSession::state() const +{ + return m_state; +} + +void CameraBinSession::setState(QCamera::State newState) +{ + if (newState == m_pendingState) + return; + + m_pendingState = newState; + +#if CAMERABIN_DEBUG + qDebug() << Q_FUNC_INFO << ENUM_NAME(QCamera, "State", newState); +#endif + + switch (newState) { + case QCamera::UnloadedState: + if (m_recordingActive) + stopVideoRecording(); + + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + m_state = newState; + if (m_busy) + emit busyChanged(m_busy = false); + + emit stateChanged(m_state); + break; + case QCamera::LoadedState: + if (m_recordingActive) + stopVideoRecording(); + + if (m_videoInputHasChanged) { + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + m_videoSrc = buildVideoSrc(); + g_object_set(m_pipeline, VIDEO_SOURCE_PROPERTY, m_videoSrc, NULL); + updateVideoSourceCaps(); + m_videoInputHasChanged = false; + } +#ifdef USE_READY_STATE_ON_LOADED + gst_element_set_state(m_pipeline, GST_STATE_READY); +#else + m_state = QCamera::LoadedState; + if (m_viewfinderInterface) + m_viewfinderInterface->stopRenderer(); + gst_element_set_state(m_pipeline, GST_STATE_NULL); + emit stateChanged(m_state); +#endif + break; + case QCamera::ActiveState: + if (setupCameraBin()) { + GstState binState = GST_STATE_NULL; + GstState pending = GST_STATE_NULL; + gst_element_get_state(m_pipeline, &binState, &pending, 0); + + if (pending == GST_STATE_VOID_PENDING && binState == GST_STATE_READY) { + m_pendingResolutionUpdate = false; + setupCaptureResolution(); + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + } else { + m_pendingResolutionUpdate = true; + gst_element_set_state(m_pipeline, GST_STATE_READY); + } + } + } +} + +bool CameraBinSession::isBusy() const +{ + return m_busy; +} + +void CameraBinSession::updateBusyStatus(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + CameraBinSession *session = reinterpret_cast<CameraBinSession *>(d); + + bool idle = false; + g_object_get(o, "idle", &idle, NULL); + bool busy = !idle; + + if (session->m_busy != busy) { + session->m_busy = busy; + QMetaObject::invokeMethod(session, "busyChanged", + Qt::QueuedConnection, + Q_ARG(bool, busy)); + } +} + +qint64 CameraBinSession::duration() const +{ + GstFormat format = GST_FORMAT_TIME; + gint64 duration = 0; + + if ( m_pipeline && gst_element_query_position(m_pipeline, &format, &duration)) + return duration / 1000000; + else + return 0; +} + +bool CameraBinSession::isMuted() const +{ + return m_muted; +} + +void CameraBinSession::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + + if (m_pipeline) + g_object_set(G_OBJECT(m_pipeline), MUTE_PROPERTY, m_muted, NULL); + emit mutedChanged(m_muted); + } +} + +void CameraBinSession::setCaptureDevice(const QString &deviceName) +{ + m_captureDevice = deviceName; +} + +void CameraBinSession::setMetaData(const QMap<QByteArray, QVariant> &data) +{ + m_metaData = data; + + if (m_pipeline) { + GstIterator *elements = gst_bin_iterate_all_by_interface(GST_BIN(m_pipeline), GST_TYPE_TAG_SETTER); + GstElement *element = 0; + while (gst_iterator_next(elements, (void**)&element) == GST_ITERATOR_OK) { + 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; + } + } + } + } +} + +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 (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); + + 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 int exposedSignalIndex = metaObject()->indexOfSignal("imageExposed(int)"); + metaObject()->method(exposedSignalIndex).invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_requestId)); + + static int signalIndex = metaObject()->indexOfSignal("imageCaptured(int,QImage)"); + metaObject()->method(signalIndex).invoke(this, + Qt::QueuedConnection, + Q_ARG(int,m_requestId), + Q_ARG(QImage,img)); + } + + } + return true; + } + } + + if (gst_structure_has_name(gm->structure, "prepare-xwindow-id")) { + if (m_viewfinderInterface) + m_viewfinderInterface->precessNewStream(); + + return true; + } + + if (gst_structure_has_name(gm->structure, GST_PHOTOGRAPHY_AUTOFOCUS_DONE)) + m_cameraFocusControl->handleFocusMessage(gm); + + if (m_viewfinderInterface && GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_viewfinderElement)) + m_viewfinderInterface->handleSyncMessage(gm); + } + + return false; +} + +void CameraBinSession::handleBusMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error (gm, &err, &debug); + + QString message; + + if (err && err->message) { + message = QString::fromUtf8(err->message); + qWarning() << "CameraBin error:" << message; + } + + //only report error messager from camerabin + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_pipeline)) { + if (message.isEmpty()) + message = tr("Camera error"); + + emit error(int(QMediaRecorder::ResourceError), message); + } + + if (err) + g_error_free (err); + + if (debug) + g_free (debug); + } + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_WARNING) { + GError *err; + gchar *debug; + gst_message_parse_warning (gm, &err, &debug); + + if (err && err->message) + qWarning() << "CameraBin warning:" << QString::fromUtf8(err->message); + + if (err) + g_error_free (err); + if (debug) + g_free (debug); + } + + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_pipeline)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_DURATION: + break; + + case GST_MESSAGE_STATE_CHANGED: + { + + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + +#if CAMERABIN_DEBUG + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); +#endif + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + if (m_state != QCamera::UnloadedState) + emit stateChanged(m_state = QCamera::UnloadedState); + break; + case GST_STATE_READY: + if (m_pendingResolutionUpdate) { + m_pendingResolutionUpdate = false; + setupCaptureResolution(); + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + } + if (m_state != QCamera::LoadedState) + emit stateChanged(m_state = QCamera::LoadedState); + break; + case GST_STATE_PAUSED: + case GST_STATE_PLAYING: + emit stateChanged(m_state = QCamera::ActiveState); + break; + } + } + break; + default: + break; + } + //qDebug() << "New session state:" << ENUM_NAME(CameraBinSession,"State",m_state); + } + + if (m_viewfinderInterface && GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_viewfinderElement)) + m_viewfinderInterface->handleBusMessage(gm); + + emit busMessage(message); + } +} + +void CameraBinSession::recordVideo() +{ + m_recordingActive = true; + m_actualSink = m_sink; + if (m_actualSink.isEmpty()) { + QString ext = m_mediaContainerControl->containerMimeType(); + m_actualSink = generateFileName("clip_", defaultDir(QCamera::CaptureVideo), ext); + } + + g_object_set(G_OBJECT(m_pipeline), FILENAME_PROPERTY, m_actualSink.toEncoded().constData(), NULL); + + g_signal_emit_by_name(G_OBJECT(m_pipeline), CAPTURE_START, NULL); +} + +void CameraBinSession::resumeVideoRecording() +{ + m_recordingActive = true; + g_signal_emit_by_name(G_OBJECT(m_pipeline), CAPTURE_START, NULL); +} + + +void CameraBinSession::pauseVideoRecording() +{ + g_signal_emit_by_name(G_OBJECT(m_pipeline), CAPTURE_PAUSE, NULL); +} + +void CameraBinSession::stopVideoRecording() +{ + m_recordingActive = false; + g_signal_emit_by_name(G_OBJECT(m_pipeline), CAPTURE_STOP, NULL); +} + +//internal, only used by CameraBinSession::supportedFrameRates. +//recursively fills the list of framerates res from value data. +static void readValue(const GValue *value, QList< QPair<int,int> > *res, bool *continuous) +{ + if (GST_VALUE_HOLDS_FRACTION(value)) { + int num = gst_value_get_fraction_numerator(value); + int denum = gst_value_get_fraction_denominator(value); + + *res << QPair<int,int>(num, denum); + } else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) { + const GValue *rateValueMin = gst_value_get_fraction_range_min(value); + const GValue *rateValueMax = gst_value_get_fraction_range_max(value); + + if (continuous) + *continuous = true; + + readValue(rateValueMin, res, continuous); + readValue(rateValueMax, res, continuous); + } else if (GST_VALUE_HOLDS_LIST(value)) { + for (uint i=0; i<gst_value_list_get_size(value); i++) { + readValue(gst_value_list_get_value(value, i), res, continuous); + } + } +} + +static bool rateLessThan(const QPair<int,int> &r1, const QPair<int,int> &r2) +{ + return r1.first*r2.second < r2.first*r1.second; +} + +QList< QPair<int,int> > CameraBinSession::supportedFrameRates(const QSize &frameSize, bool *continuous) const +{ + QList< QPair<int,int> > res; + + if (!m_sourceCaps) + return res; + + GstCaps *caps = 0; + + if (frameSize.isEmpty()) { + caps = gst_caps_copy(m_sourceCaps); + } 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); + + caps = gst_caps_intersect(m_sourceCaps, filter); + gst_caps_unref(filter); + } + + //simplify to the list of rates only: + 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"); + const GValue *oldRate = gst_structure_get_value(structure, "framerate"); + GValue rate; + memset(&rate, 0, sizeof(rate)); + g_value_init(&rate, G_VALUE_TYPE(oldRate)); + g_value_copy(oldRate, &rate); + gst_structure_remove_all_fields(structure); + gst_structure_set_value(structure, "framerate", &rate); + } + gst_caps_do_simplify(caps); + + + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + const GValue *rateValue = gst_structure_get_value(structure, "framerate"); + readValue(rateValue, &res, continuous); + } + + qSort(res.begin(), res.end(), rateLessThan); + +#if CAMERABIN_DEBUG + qDebug() << "Supported rates:" << gst_caps_to_string(caps); + qDebug() << res; +#endif + + gst_caps_unref(caps); + + return res; +} + +//internal, only used by CameraBinSession::supportedResolutions +//recursively find the supported resolutions range. +static QPair<int,int> valueRange(const GValue *value, bool *continuous) +{ + int minValue = 0; + int maxValue = 0; + + if (g_value_type_compatible(G_VALUE_TYPE(value), G_TYPE_INT)) { + minValue = maxValue = g_value_get_int(value); + } else if (GST_VALUE_HOLDS_INT_RANGE(value)) { + minValue = gst_value_get_int_range_min(value); + maxValue = gst_value_get_int_range_max(value); + *continuous = true; + } else if (GST_VALUE_HOLDS_LIST(value)) { + for (uint i=0; i<gst_value_list_get_size(value); i++) { + QPair<int,int> res = valueRange(gst_value_list_get_value(value, i), continuous); + + if (res.first > 0 && minValue > 0) + minValue = qMin(minValue, res.first); + else //select non 0 valid value + minValue = qMax(minValue, res.first); + + maxValue = qMax(maxValue, res.second); + } + } + + return QPair<int,int>(minValue, maxValue); +} + +static bool resolutionLessThan(const QSize &r1, const QSize &r2) +{ + return r1.width()*r1.height() < r2.width()*r2.height(); +} + + +QList<QSize> CameraBinSession::supportedResolutions(QPair<int,int> rate, + bool *continuous, + QCamera::CaptureMode mode) const +{ + QList<QSize> res; + + if (continuous) + *continuous = false; + + if (!m_sourceCaps) + return res; + +#if CAMERABIN_DEBUG + qDebug() << "Source caps:" << gst_caps_to_string(m_sourceCaps); +#endif + + GstCaps *caps = 0; + bool isContinuous = false; + + if (rate.first <= 0 || rate.second <= 0) { + caps = gst_caps_copy(m_sourceCaps); + } 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); + + caps = gst_caps_intersect(m_sourceCaps, filter); + gst_caps_unref(filter); + } + + //simplify to the list of resolutions only: + 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"); + const GValue *oldW = gst_structure_get_value(structure, "width"); + const GValue *oldH = gst_structure_get_value(structure, "height"); + GValue w; + memset(&w, 0, sizeof(GValue)); + GValue h; + memset(&h, 0, sizeof(GValue)); + g_value_init(&w, G_VALUE_TYPE(oldW)); + g_value_init(&h, G_VALUE_TYPE(oldH)); + g_value_copy(oldW, &w); + g_value_copy(oldH, &h); + gst_structure_remove_all_fields(structure); + gst_structure_set_value(structure, "width", &w); + gst_structure_set_value(structure, "height", &h); + } + gst_caps_do_simplify(caps); + + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + const GValue *wValue = gst_structure_get_value(structure, "width"); + const GValue *hValue = gst_structure_get_value(structure, "height"); + + QPair<int,int> wRange = valueRange(wValue, &isContinuous); + QPair<int,int> hRange = valueRange(hValue, &isContinuous); + + QSize minSize(wRange.first, hRange.first); + QSize maxSize(wRange.second, hRange.second); + + if (!minSize.isEmpty()) + res << minSize; + + if (minSize != maxSize && !maxSize.isEmpty()) + res << maxSize; + } + + + qSort(res.begin(), res.end(), resolutionLessThan); + + //if the range is continuos, populate is with the common rates + if (isContinuous && res.size() >= 2) { + //fill the ragne with common value + static QList<QSize> commonSizes = + QList<QSize>() << QSize(128, 96) + << QSize(160,120) + << QSize(176, 144) + << QSize(320, 240) + << QSize(352, 288) + << QSize(640, 480) + << QSize(848, 480) + << QSize(854, 480) + << QSize(1024, 768) + << QSize(1280, 720) // HD 720 + << QSize(1280, 1024) + << QSize(1600, 1200) + << QSize(1920, 1080) // HD + << QSize(1920, 1200) + << QSize(2048, 1536) + << QSize(2560, 1600) + << QSize(2580, 1936); + QSize minSize = res.first(); + QSize maxSize = res.last(); + +#ifdef Q_WS_MAEMO_5 + if (mode == QCamera::CaptureVideo && cameraRole() == BackCamera) + maxSize = QSize(848, 480); + if (mode == QCamera::CaptureStillImage) + minSize = QSize(640, 480); +#elif defined(Q_WS_MAEMO_6) + if (cameraRole() == FrontCamera && maxSize.width() > 640) + maxSize = QSize(640, 480); + else if (mode == QCamera::CaptureVideo && maxSize.width() > 1280) + maxSize = QSize(1280, 720); +#else + Q_UNUSED(mode); +#endif + + res.clear(); + + foreach (const QSize &candidate, commonSizes) { + int w = candidate.width(); + int h = candidate.height(); + + if (w > maxSize.width() && h > maxSize.height()) + break; + + if (w >= minSize.width() && h >= minSize.height() && + w <= maxSize.width() && h <= maxSize.height()) + res << candidate; + } + + if (res.isEmpty() || res.first() != minSize) + res.prepend(minSize); + + if (res.last() != maxSize) + res.append(maxSize); + } + +#if CAMERABIN_DEBUG + qDebug() << "Supported resolutions:" << gst_caps_to_string(caps); + qDebug() << res; +#endif + + gst_caps_unref(caps); + + if (continuous) + *continuous = isContinuous; + + return res; +} diff --git a/src/plugins/gstreamer/camerabin/camerabinsession.h b/src/plugins/gstreamer/camerabin/camerabinsession.h new file mode 100644 index 000000000..9597ea873 --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinsession.h @@ -0,0 +1,234 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINCAPTURESESSION_MAEMO_H +#define CAMERABINCAPTURESESSION_MAEMO_H + +#include <qmediarecordercontrol.h> + +#include <QtCore/qurl.h> +#include <QtCore/qdir.h> + +#include <gst/gst.h> +#include <gst/interfaces/photography.h> + +#include "qgstreamerbushelper.h" +#include "qcamera.h" + + +class QGstreamerMessage; +class QGstreamerBusHelper; +class CameraBinAudioEncoder; +class CameraBinVideoEncoder; +class CameraBinImageEncoder; +class CameraBinRecorder; +class CameraBinContainer; +class CameraBinExposure; +class CameraBinFlash; +class CameraBinFocus; +class CameraBinImageProcessing; +class CameraBinLocks; +class CameraBinCaptureDestination; +class CameraBinCaptureBufferFormat; +class QGstreamerVideoRendererInterface; + +class QGstreamerElementFactory +{ +public: + virtual GstElement *buildElement() = 0; +}; + +class CameraBinSession : public QObject, public QGstreamerSyncEventFilter +{ + Q_OBJECT + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) +public: + enum CameraRole { + FrontCamera, // Secondary camera + BackCamera // Main photo camera + }; + + CameraBinSession(QObject *parent); + ~CameraBinSession(); + + GstPhotography *photography(); + GstElement *cameraBin() { return m_pipeline; } + + CameraRole cameraRole() const; + + QList< QPair<int,int> > supportedFrameRates(const QSize &frameSize, bool *continuous) const; + QList<QSize> supportedResolutions( QPair<int,int> rate, bool *continuous, QCamera::CaptureMode mode) const; + + QCamera::CaptureMode captureMode() { return m_captureMode; } + void setCaptureMode(QCamera::CaptureMode mode); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl& sink); + + QDir defaultDir(QCamera::CaptureMode mode) const; + QString generateFileName(const QString &prefix, const QDir &dir, const QString &ext) const; + + CameraBinAudioEncoder *audioEncodeControl() const { return m_audioEncodeControl; } + CameraBinVideoEncoder *videoEncodeControl() const { return m_videoEncodeControl; } + CameraBinImageEncoder *imageEncodeControl() const { return m_imageEncodeControl; } + CameraBinExposure *cameraExposureControl() const { return m_cameraExposureControl; } + CameraBinFlash *cameraFlashControl() const { return m_cameraFlashControl; } + CameraBinFocus *cameraFocusControl() const { return m_cameraFocusControl; } + CameraBinImageProcessing *imageProcessingControl() const { return m_imageProcessingControl; } + CameraBinLocks *cameraLocksControl() const { return m_cameraLocksControl; } + CameraBinCaptureDestination *captureDestinationControl() const { return m_captureDestinationControl; } + CameraBinCaptureBufferFormat *captureBufferFormatControl() const { return m_captureBufferFormatControl; } + + + CameraBinRecorder *recorderControl() const { return m_recorderControl; } + CameraBinContainer *mediaContainerControl() const { return m_mediaContainerControl; } + + QGstreamerElementFactory *audioInput() const { return m_audioInputFactory; } + void setAudioInput(QGstreamerElementFactory *audioInput); + + QGstreamerElementFactory *videoInput() const { return m_videoInputFactory; } + void setVideoInput(QGstreamerElementFactory *videoInput); + bool isReady() const; + + QObject *viewfinder() const { return m_viewfinder; } + void setViewfinder(QObject *viewfinder); + + void captureImage(int requestId, const QString &fileName); + + QCamera::State state() const; + bool isBusy() const; + + qint64 duration() const; + + void recordVideo(); + void pauseVideoRecording(); + void resumeVideoRecording(); + void stopVideoRecording(); + + bool isMuted() const; + + bool processSyncMessage(const QGstreamerMessage &message); + +signals: + void stateChanged(QCamera::State state); + void durationChanged(qint64 duration); + void error(int error, const QString &errorString); + void imageExposed(int requestId); + void imageCaptured(int requestId, const QImage &img); + void mutedChanged(bool); + void viewfinderChanged(); + void readyChanged(bool); + void busyChanged(bool); + void busMessage(const QGstreamerMessage &message); + +public slots: + void setDevice(const QString &device); + void setState(QCamera::State); + void setCaptureDevice(const QString &deviceName); + void setMetaData(const QMap<QByteArray, QVariant>&); + void setMuted(bool); + +private slots: + void handleBusMessage(const QGstreamerMessage &message); + void handleViewfinderChange(); + +private: + bool setupCameraBin(); + void setupCaptureResolution(); + void updateVideoSourceCaps(); + GstElement *buildVideoSrc(); + static void updateBusyStatus(GObject *o, GParamSpec *p, gpointer d); + + QUrl m_sink; + QUrl m_actualSink; + bool m_recordingActive; + QString m_captureDevice; + QCamera::State m_state; + QCamera::State m_pendingState; + QString m_inputDevice; + bool m_pendingResolutionUpdate; + bool m_muted; + bool m_busy; + + QCamera::CaptureMode m_captureMode; + QMap<QByteArray, QVariant> m_metaData; + + QGstreamerElementFactory *m_audioInputFactory; + QGstreamerElementFactory *m_videoInputFactory; + QObject *m_viewfinder; + QGstreamerVideoRendererInterface *m_viewfinderInterface; + + CameraBinAudioEncoder *m_audioEncodeControl; + CameraBinVideoEncoder *m_videoEncodeControl; + CameraBinImageEncoder *m_imageEncodeControl; + CameraBinRecorder *m_recorderControl; + CameraBinContainer *m_mediaContainerControl; + CameraBinExposure *m_cameraExposureControl; + CameraBinFlash *m_cameraFlashControl; + CameraBinFocus *m_cameraFocusControl; + CameraBinImageProcessing *m_imageProcessingControl; + CameraBinLocks *m_cameraLocksControl; + CameraBinCaptureDestination *m_captureDestinationControl; + CameraBinCaptureBufferFormat *m_captureBufferFormatControl; + + QGstreamerBusHelper *m_busHelper; + GstBus* m_bus; + GstElement *m_pipeline; + GstElement *m_videoSrc; + GstElement *m_viewfinderElement; + bool m_viewfinderHasChanged; + bool m_videoInputHasChanged; + + GstCaps *m_sourceCaps; + + GstElement *m_audioSrc; + GstElement *m_audioConvert; + GstElement *m_capsFilter; + GstElement *m_fileSink; + GstElement *m_audioEncoder; + GstElement *m_muxer; + +public: + QString m_imageFileName; + int m_requestId; +}; + +#endif // CAMERABINCAPTURESESSION_MAEMO_H diff --git a/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp new file mode 100644 index 000000000..b1809abcb --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.cpp @@ -0,0 +1,346 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabinvideoencoder.h" +#include "camerabinsession.h" +#include "camerabincontainer.h" + +#include <QtCore/qdebug.h> + +CameraBinVideoEncoder::CameraBinVideoEncoder(CameraBinSession *session) + :QVideoEncoderControl(session), m_session(session) +{ + QList<QByteArray> codecCandidates; +#if defined(Q_WS_MAEMO_5) + codecCandidates << "video/mpeg4" << "video/h264" << "video/h263" << "video/theora" + << "video/mpeg2" << "video/mpeg1" << "video/mjpeg" << "video/VP8" << "video/h261"; + + m_elementNames["video/h264"] = "dsph264enc"; + m_elementNames["video/mpeg4"] = "dspmp4venc"; + m_elementNames["video/h263"] = "dsph263enc"; + m_elementNames["video/theora"] = "theoraenc"; + m_elementNames["video/mpeg2"] = "ffenc_mpeg2video"; + m_elementNames["video/mpeg1"] = "ffenc_mpeg1video"; + m_elementNames["video/mjpeg"] = "ffenc_mjpeg"; + m_elementNames["video/VP8"] = "vp8enc"; + m_elementNames["video/h261"] = "ffenc_h261"; + + m_codecOptions["video/mpeg4"] = QStringList() << "mode" << "keyframe-interval"; +#elif defined(Q_WS_MAEMO_6) + codecCandidates << "video/mpeg4" << "video/h264" << "video/h263"; + + m_elementNames["video/h264"] = "dsph264enc"; + m_elementNames["video/mpeg4"] = "dsphdmp4venc"; + m_elementNames["video/h263"] = "dsph263enc"; + + QStringList options = QStringList() << "mode" << "keyframe-interval" << "max-bitrate" << "intra-refresh"; + m_codecOptions["video/h264"] = options; + m_codecOptions["video/mpeg4"] = options; + m_codecOptions["video/h263"] = options; +#else + codecCandidates << "video/h264" << "video/xvid" << "video/mpeg4" + << "video/mpeg1" << "video/mpeg2" << "video/theora" + << "video/VP8" << "video/h261" << "video/mjpeg"; + + m_elementNames["video/h264"] = "x264enc"; + m_elementNames["video/xvid"] = "xvidenc"; + m_elementNames["video/mpeg4"] = "ffenc_mpeg4"; + m_elementNames["video/mpeg1"] = "ffenc_mpeg1video"; + m_elementNames["video/mpeg2"] = "ffenc_mpeg2video"; + m_elementNames["video/theora"] = "theoraenc"; + m_elementNames["video/mjpeg"] = "ffenc_mjpeg"; + m_elementNames["video/VP8"] = "vp8enc"; + m_elementNames["video/h261"] = "ffenc_h261"; + + m_codecOptions["video/h264"] = QStringList() << "quantizer"; + m_codecOptions["video/xvid"] = QStringList() << "quantizer" << "profile"; + m_codecOptions["video/mpeg4"] = QStringList() << "quantizer"; + m_codecOptions["video/mpeg1"] = QStringList() << "quantizer"; + m_codecOptions["video/mpeg2"] = QStringList() << "quantizer"; + m_codecOptions["video/theora"] = QStringList(); + +#endif + + foreach( const QByteArray& codecName, codecCandidates ) { + QByteArray elementName = m_elementNames[codecName]; + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + if (factory) { + m_codecs.append(codecName); + const gchar *descr = gst_element_factory_get_description(factory); + m_codecDescriptions.insert(codecName, QString::fromUtf8(descr)); + + m_streamTypes.insert(codecName, + CameraBinContainer::supportedStreamTypes(factory, GST_PAD_SRC)); + + gst_object_unref(GST_OBJECT(factory)); + } + } +} + +CameraBinVideoEncoder::~CameraBinVideoEncoder() +{ +} + +QList<QSize> CameraBinVideoEncoder::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const +{ + if (continuous) + *continuous = false; + + QPair<int,int> rate = rateAsRational(settings.frameRate()); + + //select the closest supported rational rate to settings.frameRate() + + return m_session->supportedResolutions(rate, continuous, QCamera::CaptureVideo); +} + +QList< qreal > CameraBinVideoEncoder::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const +{ + if (continuous) + *continuous = false; + + QList< qreal > res; + QPair<int,int> rate; + + foreach(rate, m_session->supportedFrameRates(settings.resolution(), continuous)) { + if (rate.second > 0) + res << qreal(rate.first)/rate.second; + } + + return res; +} + +QStringList CameraBinVideoEncoder::supportedVideoCodecs() const +{ + return m_codecs; +} + +QString CameraBinVideoEncoder::videoCodecDescription(const QString &codecName) const +{ + return m_codecDescriptions.value(codecName); +} + +QStringList CameraBinVideoEncoder::supportedEncodingOptions(const QString &codec) const +{ + return m_codecOptions.value(codec); +} + +QVariant CameraBinVideoEncoder::encodingOption(const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void CameraBinVideoEncoder::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QVideoEncoderSettings CameraBinVideoEncoder::videoSettings() const +{ + return m_videoSettings; +} + +void CameraBinVideoEncoder::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoSettings = settings; + m_userSettings = settings; + emit settingsChanged(); +} + +void CameraBinVideoEncoder::setActualVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoSettings = settings; +} + +void CameraBinVideoEncoder::resetActualSettings() +{ + m_videoSettings = m_userSettings; +} + +GstElement *CameraBinVideoEncoder::createEncoder() +{ + QString codec = m_videoSettings.codec(); + QByteArray elementName = m_elementNames.value(codec); + + GstElement *encoderElement = gst_element_factory_make( elementName.constData(), "video-encoder"); + + if (encoderElement) { + if (m_videoSettings.encodingMode() == QtMultimediaKit::ConstantQualityEncoding) { + QtMultimediaKit::EncodingQuality qualityValue = m_videoSettings.quality(); + + if (elementName == "x264enc") { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 4, NULL); + int qualityTable[] = { + 50, //VeryLow + 35, //Low + 21, //Normal + 15, //High + 8 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quantizer", qualityTable[qualityValue], NULL); + } else if (elementName == "xvidenc") { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 3, NULL); + int qualityTable[] = { + 32, //VeryLow + 12, //Low + 5, //Normal + 3, //High + 2 //VeryHigh + }; + int quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (elementName == "ffenc_mpeg4" || + elementName == "ffenc_mpeg1video" || + elementName == "ffenc_mpeg2video" ) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 2, NULL); + //quant from 1 to 30, default ~3 + double qualityTable[] = { + 20, //VeryLow + 8.0, //Low + 3.0, //Normal + 2.5, //High + 2.0 //VeryHigh + }; + double quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (elementName == "theoraenc") { + int qualityTable[] = { + 8, //VeryLow + 16, //Low + 32, //Normal + 45, //High + 60 //VeryHigh + }; + //quality from 0 to 63 + int quality = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quality", quality, NULL); + } else if (elementName == "dsph264enc" || + elementName == "dspmp4venc" || + elementName == "dsphdmp4venc" || + elementName == "dsph263enc") { + //only bitrate parameter is supported + int qualityTable[] = { + 1000000, //VeryLow + 2000000, //Low + 4000000, //Normal + 8000000, //High + 16000000 //VeryHigh + }; + int bitrate = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } else { + int bitrate = m_videoSettings.bitRate(); + if (bitrate > 0) { + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString,QVariant> options = m_options.value(codec); + QMapIterator<QString,QVariant> it(options); + while (it.hasNext()) { + it.next(); + QString option = it.key(); + QVariant value = it.value(); + + switch (value.type()) { + case QVariant::Int: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toInt(), NULL); + break; + case QVariant::Bool: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toBool(), NULL); + break; + case QVariant::Double: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toDouble(), NULL); + break; + case QVariant::String: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + + } + } + + return encoderElement; +} + +QPair<int,int> CameraBinVideoEncoder::rateAsRational(qreal frameRate) const +{ + if (frameRate > 0.001) { + //convert to rational number + QList<int> denumCandidates; + denumCandidates << 1 << 2 << 3 << 5 << 10 << 25 << 30 << 50 << 100 << 1001 << 1000; + + qreal error = 1.0; + int num = 1; + int denum = 1; + + foreach (int curDenum, denumCandidates) { + int curNum = qRound(frameRate*curDenum); + qreal curError = qAbs(qreal(curNum)/curDenum - frameRate); + + if (curError < error) { + error = curError; + num = curNum; + denum = curDenum; + } + + if (curError < 1e-8) + break; + } + + return QPair<int,int>(num,denum); + } + + return QPair<int,int>(); +} + + +QSet<QString> CameraBinVideoEncoder::supportedStreamTypes(const QString &codecName) const +{ + return m_streamTypes.value(codecName); +} diff --git a/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h new file mode 100644 index 000000000..0dd9ffbad --- /dev/null +++ b/src/plugins/gstreamer/camerabin/camerabinvideoencoder.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CAMERABINVIDEOENCODE_H +#define CAMERABINVIDEOENCODE_H + +#include <qvideoencodercontrol.h> +class CameraBinSession; + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +QT_USE_NAMESPACE + +class CameraBinVideoEncoder : public QVideoEncoderControl +{ + Q_OBJECT +public: + CameraBinVideoEncoder(CameraBinSession *session); + virtual ~CameraBinVideoEncoder(); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const; + + QList< qreal > supportedFrameRates(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const; + + QPair<int,int> rateAsRational(qreal) const; + + QStringList supportedVideoCodecs() const; + QString videoCodecDescription(const QString &codecName) const; + + QVideoEncoderSettings videoSettings() const; + void setVideoSettings(const QVideoEncoderSettings &settings); + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + + void setActualVideoSettings(const QVideoEncoderSettings&); + void resetActualSettings(); + +Q_SIGNALS: + void settingsChanged(); + +private: + CameraBinSession *m_session; + + QStringList m_codecs; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QStringList> m_codecOptions; + + QVideoEncoderSettings m_videoSettings; // backend selected settings, using m_userSettings + QVideoEncoderSettings m_userSettings; + + QMap<QString, QMap<QString, QVariant> > m_options; + QMap<QString, QSet<QString> > m_streamTypes; +}; + +#endif diff --git a/src/plugins/gstreamer/camerabuttonlistener_maemo.cpp b/src/plugins/gstreamer/camerabuttonlistener_maemo.cpp new file mode 100644 index 000000000..c10156d7a --- /dev/null +++ b/src/plugins/gstreamer/camerabuttonlistener_maemo.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabuttonlistener_maemo.h" + +#include <QtDBus/qdbusconnection.h> +#include <QtDBus/qdbusinterface.h> + +#include <QtGui/qapplication.h> +#include <QtGui/qevent.h> +#include <QtGui/qwidget.h> + + +CameraButtonListener::CameraButtonListener(QObject *parent) : + QObject(parent), + m_focusPressed(false), + m_shutterPressed(false) +{ + QDBusConnection::systemBus().connect( + QString(), + "/org/freedesktop/Hal/devices/platform_cam_launch", + "org.freedesktop.Hal.Device", + "PropertyModified", + this, + SLOT(updateShuterButtonState())); + + QDBusConnection::systemBus().connect( + QString(), + "/org/freedesktop/Hal/devices/platform_cam_focus", + "org.freedesktop.Hal.Device", + "PropertyModified", + this, + SLOT(updateFocusButtonState())); +} + + +CameraButtonListener::~CameraButtonListener() +{ +} + +void CameraButtonListener::updateFocusButtonState() +{ + QDBusInterface propertyInterface("org.freedesktop.Hal", + "/org/freedesktop/Hal/devices/platform_cam_focus", + "org.freedesktop.Hal.Device", + QDBusConnection::systemBus()); + + bool pressed = propertyInterface.call("GetProperty", "button.state.value").arguments().at(0).toBool(); + + if (m_focusPressed != pressed) { + m_focusPressed = pressed; + QWidget *window = QApplication::focusWidget(); + + if (window) { + QApplication::postEvent(window, + new QKeyEvent(pressed ? QEvent::KeyPress : QEvent::KeyRelease, + 0x01100021, //Qt::Key_CameraFocus since Qt 4.7.0 + Qt::NoModifier)); + } + } +} + +void CameraButtonListener::updateShuterButtonState() +{ + QDBusInterface propertyInterface("org.freedesktop.Hal", + "/org/freedesktop/Hal/devices/platform_cam_launch", + "org.freedesktop.Hal.Device", + QDBusConnection::systemBus()); + + bool pressed = propertyInterface.call("GetProperty", "button.state.value").arguments().at(0).toBool(); + + if (m_shutterPressed != pressed) { + m_shutterPressed = pressed; + QWidget *window = QApplication::focusWidget(); + + if (window) { + QApplication::postEvent(window, + new QKeyEvent(pressed ? QEvent::KeyPress : QEvent::KeyRelease, + 0x01100020, //Qt::Key_Camera since Qt 4.7.0 + Qt::NoModifier)); + } + } +} diff --git a/src/plugins/gstreamer/camerabuttonlistener_maemo.h b/src/plugins/gstreamer/camerabuttonlistener_maemo.h new file mode 100644 index 000000000..a4d0153ad --- /dev/null +++ b/src/plugins/gstreamer/camerabuttonlistener_maemo.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABUTTONLISTENER_MAEMO_H +#define CAMERABUTTONLISTENER_MAEMO_H + +#include <QObject> + +class CameraButtonListener : public QObject +{ + Q_OBJECT +public: + CameraButtonListener(QObject *parent = 0); + virtual ~CameraButtonListener(); + +private slots: + void updateFocusButtonState(); + void updateShuterButtonState(); + +private: + bool m_focusPressed; + bool m_shutterPressed; +}; + +#endif // CAMERABUTTONLISTENER_MAEMO_H diff --git a/src/plugins/gstreamer/camerabuttonlistener_meego.cpp b/src/plugins/gstreamer/camerabuttonlistener_meego.cpp new file mode 100644 index 000000000..8be24ba2a --- /dev/null +++ b/src/plugins/gstreamer/camerabuttonlistener_meego.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "camerabuttonlistener_meego.h" + +#include <QtGui/qapplication.h> +#include <QtGui/qevent.h> +#include <QtGui/qwidget.h> +#include <QtCore/qdebug.h> + +CameraButtonListener::CameraButtonListener(QObject *parent) : + QObject(parent), + m_focusPressed(false), + m_shutterPressed(false) +{ + m_keys = new MeeGo::QmKeys(this); + connect(m_keys, SIGNAL(keyEvent(MeeGo::QmKeys::Key, MeeGo::QmKeys::State)), + this, SLOT(handleQmKeyEvent(MeeGo::QmKeys::Key,MeeGo::QmKeys::State))); +} + +CameraButtonListener::~CameraButtonListener() +{ +} + +void CameraButtonListener::handleQmKeyEvent(MeeGo::QmKeys::Key key, MeeGo::QmKeys::State state) +{ + if (key == MeeGo::QmKeys::Camera) { + QWidget *window = QApplication::focusWidget(); + + bool focusPressed = (state == MeeGo::QmKeys::KeyHalfDown) || + (state == MeeGo::QmKeys::KeyDown); + + if (m_focusPressed != focusPressed) { + m_focusPressed = focusPressed; + if (window) { + QApplication::postEvent(window, + new QKeyEvent(focusPressed ? QEvent::KeyPress : QEvent::KeyRelease, + Qt::Key_CameraFocus, + Qt::NoModifier)); + } + } + + bool shutterPressed = (state == MeeGo::QmKeys::KeyDown); + if (m_shutterPressed != shutterPressed) { + m_shutterPressed = shutterPressed; + if (window) { + QApplication::postEvent(window, + new QKeyEvent(shutterPressed ? QEvent::KeyPress : QEvent::KeyRelease, + Qt::Key_Camera, + Qt::NoModifier)); + } + } + } +} diff --git a/src/plugins/gstreamer/camerabuttonlistener_meego.h b/src/plugins/gstreamer/camerabuttonlistener_meego.h new file mode 100644 index 000000000..88ca26788 --- /dev/null +++ b/src/plugins/gstreamer/camerabuttonlistener_meego.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef CAMERABUTTONLISTENER_MEEGO_H +#define CAMERABUTTONLISTENER_MEEGO_H + +#include <QtCore/qobject.h> +#include <qmsystem2/qmkeys.h> + +class CameraButtonListener : public QObject +{ + Q_OBJECT +public: + CameraButtonListener(QObject *parent = 0); + ~CameraButtonListener(); + +private slots: + void handleQmKeyEvent(MeeGo::QmKeys::Key key, MeeGo::QmKeys::State state); + +private: + MeeGo::QmKeys *m_keys; + bool m_focusPressed; + bool m_shutterPressed; +}; + +#endif // CAMERABUTTONLISTENER_MEEGO_H diff --git a/src/plugins/gstreamer/gstreamer.pro b/src/plugins/gstreamer/gstreamer.pro new file mode 100644 index 000000000..681fac4e2 --- /dev/null +++ b/src/plugins/gstreamer/gstreamer.pro @@ -0,0 +1,101 @@ + +load(qt_module) + +TARGET = qgstengine +QT += multimediakit-private network +PLUGIN_TYPE=mediaservice + +load(qt_plugin) +DESTDIR = $$QT.multimediakit.plugins/$${PLUGIN_TYPE} + +unix:contains(QT_CONFIG, alsa) { +DEFINES += HAVE_ALSA +LIBS += \ + -lasound +} + +CONFIG += link_pkgconfig + +PKGCONFIG += \ + gstreamer-0.10 \ + gstreamer-base-0.10 \ + gstreamer-interfaces-0.10 \ + gstreamer-audio-0.10 \ + gstreamer-video-0.10 + +maemo*:PKGCONFIG +=gstreamer-plugins-bad-0.10 +contains(gstreamer-appsrc_enabled, yes): PKGCONFIG += gstreamer-app-0.10 + +maemo5 { + HEADERS += camerabuttonlistener_maemo.h + SOURCES += camerabuttonlistener_maemo.cpp + + QT += dbus +} + +maemo6 { + HEADERS += camerabuttonlistener_meego.h + SOURCES += camerabuttonlistener_meego.cpp + + PKGCONFIG += qmsystem2 libresourceqt1 + + isEqual(QT_ARCH,armv6) { + HEADERS += qgstreamergltexturerenderer.h + SOURCES += qgstreamergltexturerenderer.cpp + QT += opengl + LIBS += -lEGL -lgstmeegointerfaces-0.10 + } +} + +# Input +HEADERS += \ + qgstreamermessage.h \ + qgstreamerbushelper.h \ + qgstreamervideorendererinterface.h \ + qgstreamerserviceplugin.h \ + qgstreameraudioinputendpointselector.h \ + qgstreamervideorenderer.h \ + qgstvideobuffer.h \ + qvideosurfacegstsink.h \ + qgstreamervideoinputdevicecontrol.h \ + gstvideoconnector.h \ + qabstractgstbufferpool.h \ + qgstutils.h + +SOURCES += \ + qgstreamermessage.cpp \ + qgstreamerbushelper.cpp \ + qgstreamervideorendererinterface.cpp \ + qgstreamerserviceplugin.cpp \ + qgstreameraudioinputendpointselector.cpp \ + qgstreamervideorenderer.cpp \ + qgstvideobuffer.cpp \ + qvideosurfacegstsink.cpp \ + qgstreamervideoinputdevicecontrol.cpp \ + gstvideoconnector.c \ + qgstutils.cpp + + +!win32:!contains(QT_CONFIG,embedded):!mac:!symbian:!simulator:!contains(QT_CONFIG, qpa) { + LIBS += -lXv -lX11 -lXext + + HEADERS += \ + qgstreamervideooverlay.h \ + qgstreamervideowindow.h \ + qgstreamervideowidget.h \ + qx11videosurface.h \ + qgstxvimagebuffer.h + + SOURCES += \ + qgstreamervideooverlay.cpp \ + qgstreamervideowindow.cpp \ + qgstreamervideowidget.cpp \ + qx11videosurface.cpp \ + qgstxvimagebuffer.cpp +} +include(mediaplayer/mediaplayer.pri) +include(mediacapture/mediacapture.pri) + +contains(gstreamer-photography_enabled, yes) { + include(camerabin/camerabin.pri) +} diff --git a/src/plugins/gstreamer/gstvideoconnector.c b/src/plugins/gstreamer/gstvideoconnector.c new file mode 100644 index 000000000..55570873e --- /dev/null +++ b/src/plugins/gstreamer/gstvideoconnector.c @@ -0,0 +1,421 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "gstvideoconnector.h" + +/* signals */ +enum +{ + SIGNAL_RESEND_NEW_SEGMENT, + SIGNAL_CONNECTION_FAILED, + LAST_SIGNAL +}; +static guint gst_video_connector_signals[LAST_SIGNAL] = { 0 }; + + +GST_DEBUG_CATEGORY_STATIC (video_connector_debug); +#define GST_CAT_DEFAULT video_connector_debug + +static GstStaticPadTemplate gst_video_connector_sink_factory = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstStaticPadTemplate gst_video_connector_src_factory = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (video_connector_debug, \ + "video-connector", 0, "An identity like element for reconnecting video stream"); + +GST_BOILERPLATE_FULL (GstVideoConnector, gst_video_connector, GstElement, + GST_TYPE_ELEMENT, _do_init); + +static void gst_video_connector_dispose (GObject * object); +static GstFlowReturn gst_video_connector_chain (GstPad * pad, GstBuffer * buf); +static GstFlowReturn gst_video_connector_buffer_alloc (GstPad * pad, + guint64 offset, guint size, GstCaps * caps, GstBuffer ** buf); +static GstStateChangeReturn gst_video_connector_change_state (GstElement * + element, GstStateChange transition); +static gboolean gst_video_connector_handle_sink_event (GstPad * pad, + GstEvent * event); +static gboolean gst_video_connector_new_buffer_probe(GstObject *pad, GstBuffer *buffer, guint * object); +static void gst_video_connector_resend_new_segment(GstElement * element, gboolean emitFailedSignal); +static gboolean gst_video_connector_setcaps (GstPad *pad, GstCaps *caps); +static GstCaps *gst_video_connector_getcaps (GstPad * pad); +static gboolean gst_video_connector_acceptcaps (GstPad * pad, GstCaps * caps); + +static void +gst_video_connector_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details_simple (element_class, "Video Connector", + "Generic", + "An identity like element used for reconnecting video stream", + "Dmytro Poplavskiy <dmytro.poplavskiy@nokia.com>"); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_video_connector_sink_factory)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_video_connector_src_factory)); +} + +static void +gst_video_connector_class_init (GstVideoConnectorClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->dispose = gst_video_connector_dispose; + gstelement_class->change_state = gst_video_connector_change_state; + klass->resend_new_segment = gst_video_connector_resend_new_segment; + + gst_video_connector_signals[SIGNAL_RESEND_NEW_SEGMENT] = + g_signal_new ("resend-new-segment", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GstVideoConnectorClass, resend_new_segment), NULL, NULL, + g_cclosure_marshal_VOID__BOOLEAN, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); + + gst_video_connector_signals[SIGNAL_CONNECTION_FAILED] = + g_signal_new ("connection-failed", G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static void +gst_video_connector_init (GstVideoConnector *element, + GstVideoConnectorClass *g_class) +{ + element->sinkpad = + gst_pad_new_from_static_template (&gst_video_connector_sink_factory, + "sink"); + gst_pad_set_chain_function(element->sinkpad, + GST_DEBUG_FUNCPTR (gst_video_connector_chain)); + gst_pad_set_event_function(element->sinkpad, + GST_DEBUG_FUNCPTR (gst_video_connector_handle_sink_event)); + gst_pad_set_bufferalloc_function(element->sinkpad, + GST_DEBUG_FUNCPTR (gst_video_connector_buffer_alloc)); + gst_pad_set_setcaps_function(element->sinkpad, + GST_DEBUG_FUNCPTR (gst_video_connector_setcaps)); + gst_pad_set_getcaps_function(element->sinkpad, + GST_DEBUG_FUNCPTR(gst_video_connector_getcaps)); + gst_pad_set_acceptcaps_function(element->sinkpad, + GST_DEBUG_FUNCPTR(gst_video_connector_acceptcaps)); + + gst_element_add_pad (GST_ELEMENT (element), element->sinkpad); + + element->srcpad = + gst_pad_new_from_static_template (&gst_video_connector_src_factory, + "src"); + gst_pad_add_buffer_probe(element->srcpad, + G_CALLBACK(gst_video_connector_new_buffer_probe), element); + gst_element_add_pad (GST_ELEMENT (element), element->srcpad); + + element->relinked = FALSE; + element->failedSignalEmited = FALSE; + gst_segment_init (&element->segment, GST_FORMAT_TIME); + element->latest_buffer = NULL; +} + +static void +gst_video_connector_reset (GstVideoConnector * element) +{ + element->relinked = FALSE; + element->failedSignalEmited = FALSE; + if (element->latest_buffer != NULL) { + gst_buffer_unref (element->latest_buffer); + element->latest_buffer = NULL; + } + gst_segment_init (&element->segment, GST_FORMAT_UNDEFINED); +} + +static void +gst_video_connector_dispose (GObject * object) +{ + GstVideoConnector *element = GST_VIDEO_CONNECTOR (object); + + gst_video_connector_reset (element); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static GstFlowReturn +gst_video_connector_buffer_alloc (GstPad * pad, guint64 offset, guint size, + GstCaps * caps, GstBuffer ** buf) +{ + GstVideoConnector *element; + GstFlowReturn res = GST_FLOW_OK; + element = GST_VIDEO_CONNECTOR (GST_PAD_PARENT (pad)); + + if (!buf) + return GST_FLOW_ERROR; + *buf = NULL; + + GST_OBJECT_LOCK (element); + gst_object_ref(element->srcpad); + GST_OBJECT_UNLOCK (element); + + res = gst_pad_alloc_buffer(element->srcpad, offset, size, caps, buf); + gst_object_unref (element->srcpad); + + GST_DEBUG_OBJECT (element, "buffer alloc finished: %s", gst_flow_get_name (res)); + + return res; +} + +static gboolean +gst_video_connector_setcaps (GstPad *pad, GstCaps *caps) +{ + GstVideoConnector *element; + element = GST_VIDEO_CONNECTOR (GST_PAD_PARENT (pad)); + + /* forward-negotiate */ + gboolean res = gst_pad_set_caps(element->srcpad, caps); + + GST_DEBUG_OBJECT(element, "gst_video_connector_setcaps %s %i", gst_caps_to_string(caps), res); + + if (!res) { + //if set_caps failed, emit "connection-failed" signal + //so colorspace transformation elemnt can be inserted + GST_INFO_OBJECT(element, "gst_video_connector_setcaps failed, emit connection-failed signal"); + g_signal_emit(G_OBJECT(element), gst_video_connector_signals[SIGNAL_CONNECTION_FAILED], 0); + + return gst_pad_set_caps(element->srcpad, caps); + } + + return TRUE; +} + +static GstCaps *gst_video_connector_getcaps (GstPad * pad) +{ + GstVideoConnector *element; + element = GST_VIDEO_CONNECTOR (GST_PAD_PARENT (pad)); + +#if (GST_VERSION_MICRO > 25) + GstCaps *caps = gst_pad_peer_get_caps_reffed(element->srcpad); +#else + GstCaps *caps = gst_pad_peer_get_caps(element->srcpad); +#endif + + if (!caps) + caps = gst_caps_new_any(); + + return caps; +} + +static gboolean gst_video_connector_acceptcaps (GstPad * pad, GstCaps * caps) +{ + GstVideoConnector *element; + element = GST_VIDEO_CONNECTOR (GST_PAD_PARENT (pad)); + + return gst_pad_peer_accept_caps(element->srcpad, caps); +} + +static void +gst_video_connector_resend_new_segment(GstElement * element, gboolean emitFailedSignal) +{ + GST_INFO_OBJECT(element, "New segment requested, failed signal enabled: %i", emitFailedSignal); + GstVideoConnector *connector = GST_VIDEO_CONNECTOR(element); + connector->relinked = TRUE; + if (emitFailedSignal) + connector->failedSignalEmited = FALSE; +} + + +static gboolean gst_video_connector_new_buffer_probe(GstObject *pad, GstBuffer *buffer, guint * object) +{ + GstVideoConnector *element = GST_VIDEO_CONNECTOR (object); + + /* + If relinking is requested, the current buffer should be rejected and + the new segment + previous buffer should be pushed first + */ + + if (element->relinked) + GST_LOG_OBJECT(element, "rejected buffer because of new segment request"); + + return !element->relinked; +} + + +static GstFlowReturn +gst_video_connector_chain (GstPad * pad, GstBuffer * buf) +{ + GstFlowReturn res; + GstVideoConnector *element; + + element = GST_VIDEO_CONNECTOR (gst_pad_get_parent (pad)); + + do { + /* + Resend the segment message and last buffer to preroll the new sink. + Sinks can be changed multiple times while paused, + while loop allows to send the segment message and preroll + all of them with the same buffer. + */ + while (element->relinked) { + element->relinked = FALSE; + + gint64 pos = element->segment.last_stop; + + if (element->latest_buffer && GST_BUFFER_TIMESTAMP_IS_VALID(element->latest_buffer)) { + pos = GST_BUFFER_TIMESTAMP (element->latest_buffer); + } + + //push a new segment and last buffer + GstEvent *ev = gst_event_new_new_segment (TRUE, + element->segment.rate, + element->segment.format, + pos, //start + element->segment.stop, + pos); + + GST_DEBUG_OBJECT (element, "Pushing new segment event"); + if (!gst_pad_push_event (element->srcpad, ev)) { + GST_WARNING_OBJECT (element, + "Newsegment handling failed in %" GST_PTR_FORMAT, + element->srcpad); + } + + if (element->latest_buffer) { + GST_DEBUG_OBJECT (element, "Pushing latest buffer..."); + gst_buffer_ref(element->latest_buffer); + gst_pad_push(element->srcpad, element->latest_buffer); + } + } + + gst_buffer_ref(buf); + + //it's possible video sink is changed during gst_pad_push blocked by + //pad lock, in this case ( element->relinked == TRUE ) + //the buffer should be rejected by the buffer probe and + //the new segment + prev buffer should be sent before + + GST_LOG_OBJECT (element, "Pushing buffer..."); + res = gst_pad_push (element->srcpad, buf); + GST_LOG_OBJECT (element, "Pushed buffer: %s", gst_flow_get_name (res)); + + //if gst_pad_push failed give the service another chance, + //it may still work with the colorspace element added + if (!element->failedSignalEmited && res == GST_FLOW_NOT_NEGOTIATED) { + element->failedSignalEmited = TRUE; + GST_INFO_OBJECT(element, "gst_pad_push failed, emit connection-failed signal"); + g_signal_emit(G_OBJECT(element), gst_video_connector_signals[SIGNAL_CONNECTION_FAILED], 0); + } + + } while (element->relinked); + + + if (element->latest_buffer) { + gst_buffer_unref (element->latest_buffer); + element->latest_buffer = NULL; + } + + //don't save the last video buffer on maemo6 because of buffers shortage + //with omapxvsink +#ifndef Q_WS_MAEMO_6 + element->latest_buffer = gst_buffer_ref(buf); +#endif + + gst_buffer_unref(buf); + gst_object_unref (element); + + return res; +} + +static GstStateChangeReturn +gst_video_connector_change_state (GstElement * element, + GstStateChange transition) +{ + GstVideoConnector *connector; + GstStateChangeReturn result; + + connector = GST_VIDEO_CONNECTOR(element); + result = GST_ELEMENT_CLASS (parent_class)->change_state(element, transition); + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_video_connector_reset (connector); + break; + case GST_STATE_CHANGE_READY_TO_PAUSED: + connector->relinked = FALSE; + break; + default: + break; + } + + return result; +} + +static gboolean +gst_video_connector_handle_sink_event (GstPad * pad, GstEvent * event) +{ + if (GST_EVENT_TYPE (event) == GST_EVENT_NEWSEGMENT) { + GstVideoConnector *element = GST_VIDEO_CONNECTOR (gst_pad_get_parent (pad)); + + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, + &start, &stop, &time); + + GST_LOG_OBJECT (element, + "NEWSEGMENT update %d, rate %lf, applied rate %lf, " + "format %d, " "%" G_GINT64_FORMAT " -- %" G_GINT64_FORMAT ", time %" + G_GINT64_FORMAT, update, rate, arate, format, start, stop, time); + + gst_segment_set_newsegment_full (&element->segment, update, + rate, arate, format, start, stop, time); + + gst_object_unref (element); + } + + return gst_pad_event_default (pad, event); +} diff --git a/src/plugins/gstreamer/gstvideoconnector.h b/src/plugins/gstreamer/gstvideoconnector.h new file mode 100644 index 000000000..00d057e06 --- /dev/null +++ b/src/plugins/gstreamer/gstvideoconnector.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTVIDEOCONNECTOR_H +#define QGSTVIDEOCONNECTOR_H + +#include <gst/gst.h> + +G_BEGIN_DECLS + +#define GST_TYPE_VIDEO_CONNECTOR \ + (gst_video_connector_get_type()) +#define GST_VIDEO_CONNECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_VIDEO_CONNECTOR, GstVideoConnector)) +#define GST_VIDEO_CONNECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_VIDEO_CONNECTOR, GstVideoConnectorClass)) +#define GST_IS_VIDEO_CONNECTOR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_VIDEO_CONNECTOR)) +#define GST_IS_VIDEO_CONNECTOR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_VIDEO_CONNECTOR)) + +typedef struct _GstVideoConnector GstVideoConnector; +typedef struct _GstVideoConnectorClass GstVideoConnectorClass; + +struct _GstVideoConnector { + GstElement element; + + GstPad *srcpad; + GstPad *sinkpad; + + gboolean relinked; + gboolean failedSignalEmited; + GstSegment segment; + GstBuffer *latest_buffer; +}; + +struct _GstVideoConnectorClass { + GstElementClass parent_class; + + /* action signal to resend new segment */ + void (*resend_new_segment) (GstElement * element, gboolean emitFailedSignal); +}; + +GType gst_video_connector_get_type (void); + +G_END_DECLS + +#endif + diff --git a/src/plugins/gstreamer/mediacapture/mediacapture.pri b/src/plugins/gstreamer/mediacapture/mediacapture.pri new file mode 100644 index 000000000..b7f7794f9 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/mediacapture.pri @@ -0,0 +1,27 @@ +INCLUDEPATH += $$PWD + +DEFINES += QMEDIA_GSTREAMER_CAPTURE + +HEADERS += $$PWD/qgstreamercaptureservice.h \ + $$PWD/qgstreamercapturesession.h \ + $$PWD/qgstreameraudioencode.h \ + $$PWD/qgstreamervideoencode.h \ + $$PWD/qgstreamerrecordercontrol.h \ + $$PWD/qgstreamermediacontainercontrol.h \ + $$PWD/qgstreamercameracontrol.h \ + $$PWD/qgstreamerv4l2input.h \ + $$PWD/qgstreamercapturemetadatacontrol.h \ + $$PWD/qgstreamerimagecapturecontrol.h \ + $$PWD/qgstreamerimageencode.h + +SOURCES += $$PWD/qgstreamercaptureservice.cpp \ + $$PWD/qgstreamercapturesession.cpp \ + $$PWD/qgstreameraudioencode.cpp \ + $$PWD/qgstreamervideoencode.cpp \ + $$PWD/qgstreamerrecordercontrol.cpp \ + $$PWD/qgstreamermediacontainercontrol.cpp \ + $$PWD/qgstreamercameracontrol.cpp \ + $$PWD/qgstreamerv4l2input.cpp \ + $$PWD/qgstreamercapturemetadatacontrol.cpp \ + $$PWD/qgstreamerimagecapturecontrol.cpp \ + $$PWD/qgstreamerimageencode.cpp diff --git a/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.cpp b/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.cpp new file mode 100644 index 000000000..e548393eb --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.cpp @@ -0,0 +1,292 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreameraudioencode.h" +#include "qgstreamercapturesession.h" +#include "qgstreamermediacontainercontrol.h" + +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerAudioEncode::QGstreamerAudioEncode(QObject *parent) + :QAudioEncoderControl(parent) +{ + QList<QByteArray> codecCandidates; + +#if defined(Q_WS_MAEMO_5) + codecCandidates << "audio/PCM"; //<< "audio/AMR" << "audio/AMR-WB" << "audio/speex"; +#elif defined(Q_WS_MAEMO_6) + codecCandidates << "audio/AAC" << "audio/mpeg" << "audio/vorbis" << "audio/speex" << "audio/GSM" + << "audio/PCM" << "audio/AMR" << "audio/AMR-WB" << "audio/FLAC"; +#else + codecCandidates << "audio/mpeg" << "audio/vorbis" << "audio/speex" << "audio/GSM" + << "audio/PCM" << "audio/AMR" << "audio/AMR-WB" << "audio/FLAC"; +#endif + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + m_elementNames["audio/AMR"] = "nokiaamrnbenc"; + m_elementNames["audio/AMR-WB"] = "nokiaamrwbenc"; + m_elementNames["audio/AAC"] = "nokiaaacenc"; +#else + m_elementNames["audio/mpeg"] = "lamemp3enc"; + m_elementNames["audio/AMR"] = "amrnbenc"; + m_elementNames["audio/AMR-WB"] = "amrwbenc"; +#endif + + m_elementNames["audio/vorbis"] = "vorbisenc"; + m_elementNames["audio/speex"] = "speexenc"; + m_elementNames["audio/PCM"] = "audioresample"; + m_elementNames["audio/FLAC"] = "flacenc"; + m_elementNames["audio/GSM"] = "gsmenc"; + + m_codecOptions["audio/vorbis"] = QStringList() << "min-bitrate" << "max-bitrate"; + m_codecOptions["audio/mpeg"] = QStringList() << "mode"; + m_codecOptions["audio/speex"] = QStringList() << "mode" << "vbr" << "vad" << "dtx"; + m_codecOptions["audio/GSM"] = QStringList(); + m_codecOptions["audio/PCM"] = QStringList(); + m_codecOptions["audio/AMR"] = QStringList(); + m_codecOptions["audio/AMR-WB"] = QStringList(); + + foreach( const QByteArray& codecName, codecCandidates ) { + QByteArray elementName = m_elementNames[codecName]; + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + + if (factory) { + m_codecs.append(codecName); + const gchar *descr = gst_element_factory_get_description(factory); + + if (codecName == QByteArray("audio/PCM")) + m_codecDescriptions.insert(codecName, tr("Raw PCM audio")); + else + m_codecDescriptions.insert(codecName, QString::fromUtf8(descr)); + + m_streamTypes.insert(codecName, + QGstreamerMediaContainerControl::supportedStreamTypes(factory, GST_PAD_SRC)); + + gst_object_unref(GST_OBJECT(factory)); + } + } + + //if (!m_codecs.isEmpty()) + // m_audioSettings.setCodec(m_codecs[0]); +} + +QGstreamerAudioEncode::~QGstreamerAudioEncode() +{ +} + +QStringList QGstreamerAudioEncode::supportedAudioCodecs() const +{ + return m_codecs; +} + +QString QGstreamerAudioEncode::codecDescription(const QString &codecName) const +{ + return m_codecDescriptions.value(codecName); +} + +QStringList QGstreamerAudioEncode::supportedEncodingOptions(const QString &codec) const +{ + return m_codecOptions.value(codec); +} + +QVariant QGstreamerAudioEncode::encodingOption( + const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void QGstreamerAudioEncode::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QList<int> QGstreamerAudioEncode::supportedSampleRates(const QAudioEncoderSettings &, bool *) const +{ + //TODO check element caps to find actual values + + return QList<int>(); +} + +QAudioEncoderSettings QGstreamerAudioEncode::audioSettings() const +{ + return m_audioSettings; +} + +void QGstreamerAudioEncode::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_audioSettings = settings; +} + + +GstElement *QGstreamerAudioEncode::createEncoder() +{ + QString codec = m_audioSettings.codec(); + GstElement *encoderElement = gst_element_factory_make(m_elementNames.value(codec).constData(), NULL); + if (!encoderElement) + return 0; + + GstBin * encoderBin = GST_BIN(gst_bin_new("audio-encoder-bin")); + + GstElement *capsFilter = gst_element_factory_make("capsfilter", NULL); + + gst_bin_add(encoderBin, capsFilter); + gst_bin_add(encoderBin, encoderElement); + gst_element_link(capsFilter, encoderElement); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(capsFilter, "sink"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(encoderElement, "src"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + 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); + + if (m_audioSettings.sampleRate() > 0) + gst_structure_set(structure, "rate", G_TYPE_INT, m_audioSettings.sampleRate(), NULL ); + + if (m_audioSettings.channelCount() > 0) + gst_structure_set(structure, "channels", G_TYPE_INT, m_audioSettings.channelCount(), NULL ); + + gst_caps_append_structure(caps,structure); + + //qDebug() << "set caps filter:" << gst_caps_to_string(caps); + + g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL); + } + + if (encoderElement) { + if (m_audioSettings.encodingMode() == QtMultimediaKit::ConstantQualityEncoding) { + QtMultimediaKit::EncodingQuality qualityValue = m_audioSettings.quality(); + + if (codec == QLatin1String("audio/vorbis")) { + double qualityTable[] = { + 0.1, //VeryLow + 0.3, //Low + 0.5, //Normal + 0.7, //High + 1.0 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", qualityTable[qualityValue], NULL); + } else if (codec == QLatin1String("audio/mpeg")) { + g_object_set(G_OBJECT(encoderElement), "target", 0, NULL); //constant quality mode + qreal quality[] = { + 1, //VeryLow + 3, //Low + 5, //Normal + 7, //High + 9 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", quality[qualityValue], NULL); + } else if (codec == QLatin1String("audio/speex")) { + //0-10 range with default 8 + double qualityTable[] = { + 2, //VeryLow + 5, //Low + 8, //Normal + 9, //High + 10 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quality", qualityTable[qualityValue], NULL); + } else if (codec.startsWith("audio/AMR")) { + int band[] = { + 0, //VeryLow + 2, //Low + 4, //Normal + 6, //High + 7 //VeryHigh + }; + + g_object_set(G_OBJECT(encoderElement), "band-mode", band[qualityValue], NULL); + } + } else { + int bitrate = m_audioSettings.bitRate(); + if (bitrate > 0) { + if (codec == QLatin1String("audio/mpeg")) { + g_object_set(G_OBJECT(encoderElement), "target", 1, NULL); //constant bitrate mode + } + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString, QVariant> options = m_options.value(codec); + QMapIterator<QString,QVariant> it(options); + while (it.hasNext()) { + it.next(); + QString option = it.key(); + QVariant value = it.value(); + + switch (value.type()) { + case QVariant::Int: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toInt(), NULL); + break; + case QVariant::Bool: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toBool(), NULL); + break; + case QVariant::Double: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toDouble(), NULL); + break; + case QVariant::String: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + + } + } + + return GST_ELEMENT(encoderBin); +} + + +QSet<QString> QGstreamerAudioEncode::supportedStreamTypes(const QString &codecName) const +{ + return m_streamTypes.value(codecName); +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.h b/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.h new file mode 100644 index 000000000..e2c48234d --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreameraudioencode.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERAUDIOENCODE_H +#define QGSTREAMERAUDIOENCODE_H + +#include <qaudioencodercontrol.h> +class QGstreamerCaptureSession; + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +#include <qaudioformat.h> + +QT_USE_NAMESPACE + +class QGstreamerAudioEncode : public QAudioEncoderControl +{ + Q_OBJECT +public: + QGstreamerAudioEncode(QObject *parent); + virtual ~QGstreamerAudioEncode(); + + QStringList supportedAudioCodecs() const; + QString codecDescription(const QString &codecName) const; + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + QList<int> supportedSampleRates(const QAudioEncoderSettings &settings = QAudioEncoderSettings(), + bool *isContinuous = 0) const; + QList<int> supportedChannelCounts(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + QList<int> supportedSampleSizes(const QAudioEncoderSettings &settings = QAudioEncoderSettings()) const; + + QAudioEncoderSettings audioSettings() const; + void setAudioSettings(const QAudioEncoderSettings&); + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + +private: + QStringList m_codecs; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QStringList> m_codecOptions; + + QMap<QString, QMap<QString, QVariant> > m_options; + + QMap<QString, QSet<QString> > m_streamTypes; + + QAudioEncoderSettings m_audioSettings; +}; + +#endif diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.cpp new file mode 100644 index 000000000..927a827dd --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamercameracontrol.h" +#include "qgstreamerimageencode.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + + +QGstreamerCameraControl::QGstreamerCameraControl(QGstreamerCaptureSession *session) + :QCameraControl(session), + m_captureMode(QCamera::CaptureStillImage), + m_session(session), + m_state(QCamera::UnloadedState), + m_status(QCamera::UnloadedStatus), + m_reloadPending(false) + +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), + this, SLOT(updateStatus())); + + connect(m_session->imageEncodeControl(), SIGNAL(settingsChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(viewfinderChanged()), + SLOT(reloadLater())); + connect(m_session, SIGNAL(readyChanged(bool)), + SLOT(reloadLater())); +} + +QGstreamerCameraControl::~QGstreamerCameraControl() +{ +} + +void QGstreamerCameraControl::setCaptureMode(QCamera::CaptureMode mode) +{ + if (m_captureMode == mode) + return; + + switch (mode) { + case QCamera::CaptureStillImage: + m_session->setCaptureMode(QGstreamerCaptureSession::Image); + break; + case QCamera::CaptureVideo: + m_session->setCaptureMode(QGstreamerCaptureSession::AudioAndVideo); + break; + } + + emit captureModeChanged(mode); + updateStatus(); + reloadLater(); +} + +void QGstreamerCameraControl::setState(QCamera::State state) +{ + if (m_state == state) + return; + + m_state = state; + switch (state) { + case QCamera::UnloadedState: + case QCamera::LoadedState: + m_session->setState(QGstreamerCaptureSession::StoppedState); + break; + case QCamera::ActiveState: + //postpone changing to Active if the session is nor ready yet + if (m_session->isReady()) { + m_session->setState(QGstreamerCaptureSession::PreviewState); + } else { +#ifdef CAMEABIN_DEBUG + qDebug() << "Camera session is not ready yet, postpone activating"; +#endif + } + break; + default: + emit error(QCamera::NotSupportedFeatureError, tr("State not supported.")); + } + + updateStatus(); + emit stateChanged(m_state); +} + +QCamera::State QGstreamerCameraControl::state() const +{ + return m_state; +} + +void QGstreamerCameraControl::updateStatus() +{ + QCamera::Status oldStatus = m_status; + + switch (m_state) { + case QCamera::UnloadedState: + m_status = QCamera::UnloadedStatus; + break; + case QCamera::LoadedState: + m_status = QCamera::LoadedStatus; + break; + case QCamera::ActiveState: + if (m_session->state() == QGstreamerCaptureSession::StoppedState) + m_status = QCamera::StartingStatus; + else + m_status = QCamera::ActiveStatus; + break; + } + + if (oldStatus != m_status) { + //qDebug() << "Status changed:" << m_status; + emit statusChanged(m_status); + } +} + +void QGstreamerCameraControl::reloadLater() +{ + //qDebug() << "reload pipeline requested"; + if (!m_reloadPending && m_state == QCamera::ActiveState) { + m_reloadPending = true; + m_session->setState(QGstreamerCaptureSession::StoppedState); + QMetaObject::invokeMethod(this, "reloadPipeline", Qt::QueuedConnection); + } +} + +void QGstreamerCameraControl::reloadPipeline() +{ + //qDebug() << "reload pipeline"; + if (m_reloadPending) { + m_reloadPending = false; + if (m_state == QCamera::ActiveState && m_session->isReady()) { + m_session->setState(QGstreamerCaptureSession::PreviewState); + } + } +} + +bool QGstreamerCameraControl::canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const +{ + Q_UNUSED(status); + + switch (changeType) { + case QCameraControl::CaptureMode: + case QCameraControl::ImageEncodingSettings: + case QCameraControl::VideoEncodingSettings: + case QCameraControl::Viewfinder: + return true; + default: + return false; + } +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.h b/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.h new file mode 100644 index 000000000..c07ffcd19 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercameracontrol.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERCAMERACONTROL_H +#define QGSTREAMERCAMERACONTROL_H + +#include <QHash> +#include <qcameracontrol.h> +#include "qgstreamercapturesession.h" + +QT_USE_NAMESPACE +QT_USE_NAMESPACE + +class QGstreamerCameraControl : public QCameraControl +{ + Q_OBJECT +public: + QGstreamerCameraControl( QGstreamerCaptureSession *session ); + virtual ~QGstreamerCameraControl(); + + bool isValid() const { return true; } + + QCamera::State state() const; + void setState(QCamera::State state); + + QCamera::Status status() const { return m_status; } + + QCamera::CaptureMode captureMode() const { return m_captureMode; } + void setCaptureMode(QCamera::CaptureMode mode); + + bool isCaptureModeSupported(QCamera::CaptureMode mode) const + { + return mode == QCamera::CaptureStillImage || mode == QCamera::CaptureVideo; + } + + QCamera::LockTypes supportedLocks() const + { + return QCamera::NoLock; + } + + bool canChangeProperty(PropertyChangeType changeType, QCamera::Status status) const; + +public slots: + void reloadLater(); + +private slots: + void updateStatus(); + void reloadPipeline(); + + +private: + QCamera::CaptureMode m_captureMode; + QGstreamerCaptureSession *m_session; + QCamera::State m_state; + QCamera::Status m_status; + bool m_reloadPending; +}; + +#endif // QGSTREAMERCAMERACONTROL_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp new file mode 100644 index 000000000..b0e589dc8 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.cpp @@ -0,0 +1,198 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamercapturemetadatacontrol.h" + +#include <gst/gst.h> +#include <gst/gstversion.h> + +struct QGstreamerMetaDataKeyLookup +{ + QtMultimediaKit::MetaData key; + const char *token; +}; + +static const QGstreamerMetaDataKeyLookup qt_gstreamerMetaDataKeys[] = +{ + { QtMultimediaKit::Title, GST_TAG_TITLE }, + //{ QtMultimediaKit::SubTitle, 0 }, + //{ QtMultimediaKit::Author, 0 }, + { QtMultimediaKit::Comment, GST_TAG_COMMENT }, + { QtMultimediaKit::Description, GST_TAG_DESCRIPTION }, + //{ QtMultimediaKit::Category, 0 }, + { QtMultimediaKit::Genre, GST_TAG_GENRE }, + //{ QtMultimediaKit::Year, 0 }, + //{ QtMultimediaKit::UserRating, 0 }, + + { QtMultimediaKit::Language, GST_TAG_LANGUAGE_CODE }, + + { QtMultimediaKit::Publisher, GST_TAG_ORGANIZATION }, + { QtMultimediaKit::Copyright, GST_TAG_COPYRIGHT }, + //{ QtMultimediaKit::ParentalRating, 0 }, + //{ QtMultimediaKit::RatingOrganisation, 0 }, + + // Media + //{ QtMultimediaKit::Size, 0 }, + //{ QtMultimediaKit::MediaType, 0 }, + { QtMultimediaKit::Duration, GST_TAG_DURATION }, + + // Audio + { QtMultimediaKit::AudioBitRate, GST_TAG_BITRATE }, + { QtMultimediaKit::AudioCodec, GST_TAG_AUDIO_CODEC }, + //{ QtMultimediaKit::ChannelCount, 0 }, + //{ QtMultimediaKit::SampleRate, 0 }, + + // Music + { QtMultimediaKit::AlbumTitle, GST_TAG_ALBUM }, + { QtMultimediaKit::AlbumArtist, GST_TAG_ARTIST}, + { QtMultimediaKit::ContributingArtist, GST_TAG_PERFORMER }, +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + { QtMultimediaKit::Composer, GST_TAG_COMPOSER }, +#endif + //{ QtMultimediaKit::Conductor, 0 }, + //{ QtMultimediaKit::Lyrics, 0 }, + //{ QtMultimediaKit::Mood, 0 }, + { QtMultimediaKit::TrackNumber, GST_TAG_TRACK_NUMBER }, + + //{ QtMultimediaKit::CoverArtUrlSmall, 0 }, + //{ QtMultimediaKit::CoverArtUrlLarge, 0 }, + + // Image/Video + //{ QtMultimediaKit::Resolution, 0 }, + //{ QtMultimediaKit::PixelAspectRatio, 0 }, + + // Video + //{ QtMultimediaKit::VideoFrameRate, 0 }, + //{ QtMultimediaKit::VideoBitRate, 0 }, + { QtMultimediaKit::VideoCodec, GST_TAG_VIDEO_CODEC }, + + //{ QtMultimediaKit::PosterUrl, 0 }, + + // Movie + //{ QtMultimediaKit::ChapterNumber, 0 }, + //{ QtMultimediaKit::Director, 0 }, + { QtMultimediaKit::LeadPerformer, GST_TAG_PERFORMER }, + //{ QtMultimediaKit::Writer, 0 }, + + // Photos + //{ QtMultimediaKit::CameraManufacturer, 0 }, + //{ QtMultimediaKit::CameraModel, 0 }, + //{ QtMultimediaKit::Event, 0 }, + //{ QtMultimediaKit::Subject, 0 } +}; + +QGstreamerCaptureMetaDataControl::QGstreamerCaptureMetaDataControl(QObject *parent) + :QMetaDataWriterControl(parent) +{ +} + +QVariant QGstreamerCaptureMetaDataControl::metaData(QtMultimediaKit::MetaData key) const +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + const char *name = qt_gstreamerMetaDataKeys[i].token; + + return m_values.value(QByteArray::fromRawData(name, qstrlen(name))); + } + } + return QVariant(); +} + +void QGstreamerCaptureMetaDataControl::setMetaData(QtMultimediaKit::MetaData key, const QVariant &value) +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + const char *name = qt_gstreamerMetaDataKeys[i].token; + + m_values.insert(QByteArray::fromRawData(name, qstrlen(name)), value); + + emit QMetaDataWriterControl::metaDataChanged(); + emit metaDataChanged(m_values); + + return; + } + } +} + +QList<QtMultimediaKit::MetaData> QGstreamerCaptureMetaDataControl::availableMetaData() const +{ + static QMap<QByteArray, QtMultimediaKit::MetaData> keysMap; + if (keysMap.isEmpty()) { + const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + for (int i = 0; i < count; ++i) { + keysMap[QByteArray(qt_gstreamerMetaDataKeys[i].token)] = qt_gstreamerMetaDataKeys[i].key; + } + } + + QList<QtMultimediaKit::MetaData> res; + foreach (const QByteArray &key, m_values.keys()) { + QtMultimediaKit::MetaData tag = keysMap.value(key, QtMultimediaKit::MetaData(-1)); + if (tag != -1) + res.append(tag); + } + + return res; +} + +QVariant QGstreamerCaptureMetaDataControl::extendedMetaData(QString const &name) const +{ + return m_values.value(name.toLatin1()); +} + +void QGstreamerCaptureMetaDataControl::setExtendedMetaData(QString const &name, QVariant const &value) +{ + m_values.insert(name.toLatin1(), value); + emit QMetaDataWriterControl::metaDataChanged(); + emit metaDataChanged(m_values); +} + +QStringList QGstreamerCaptureMetaDataControl::availableExtendedMetaData() const +{ + QStringList res; + foreach (const QByteArray &key, m_values.keys()) + res.append(QString(key)); + + return res; +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.h b/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.h new file mode 100644 index 000000000..e859da486 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercapturemetadatacontrol.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERCAPTUREMETADATACONTROL_H +#define QGSTREAMERCAPTUREMETADATACONTROL_H + +#include <qmetadatawritercontrol.h> + +QT_USE_NAMESPACE + +class QGstreamerCaptureMetaDataControl : public QMetaDataWriterControl +{ + Q_OBJECT +public: + QGstreamerCaptureMetaDataControl(QObject *parent); + virtual ~QGstreamerCaptureMetaDataControl() {}; + + + bool isMetaDataAvailable() const { return true; } + bool isWritable() const { return true; } + + QVariant metaData(QtMultimediaKit::MetaData key) const; + void setMetaData(QtMultimediaKit::MetaData key, const QVariant &value); + QList<QtMultimediaKit::MetaData> availableMetaData() const; + + QVariant extendedMetaData(QString const &name) const; + void setExtendedMetaData(QString const &name, QVariant const &value); + QStringList availableExtendedMetaData() const; + +Q_SIGNALS: + void metaDataChanged(const QMap<QByteArray, QVariant>&); + +private: + QMap<QByteArray, QVariant> m_values; +}; + +#endif // QGSTREAMERCAPTUREMETADATACONTROL_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp new file mode 100644 index 000000000..97310d199 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.cpp @@ -0,0 +1,185 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamercaptureservice.h" +#include "qgstreamercapturesession.h" +#include "qgstreamerrecordercontrol.h" +#include "qgstreamermediacontainercontrol.h" +#include "qgstreameraudioencode.h" +#include "qgstreamervideoencode.h" +#include "qgstreamerimageencode.h" +#include "qgstreamerbushelper.h" +#include "qgstreamercameracontrol.h" +#include "qgstreamerv4l2input.h" +#include "qgstreamercapturemetadatacontrol.h" + +#include "qgstreameraudioinputendpointselector.h" +#include "qgstreamervideoinputdevicecontrol.h" +#include "qgstreamerimagecapturecontrol.h" + +#include "qgstreamervideooverlay.h" +#include "qgstreamervideorenderer.h" + +#include "qgstreamervideowidget.h" + +#include <qmediaserviceprovider.h> + + +QGstreamerCaptureService::QGstreamerCaptureService(const QString &service, QObject *parent): + QMediaService(parent) +{ + m_captureSession = 0; + m_cameraControl = 0; + m_metaDataControl = 0; + + m_videoInput = 0; + m_audioInputEndpointSelector = 0; + m_videoInputDevice = 0; + + m_videoOutput = 0; + m_videoRenderer = 0; + m_videoWindow = 0; + m_videoWidgetControl = 0; + m_imageCaptureControl = 0; + + if (service == Q_MEDIASERVICE_AUDIOSOURCE) { + m_captureSession = new QGstreamerCaptureSession(QGstreamerCaptureSession::Audio, this); + } + + if (service == Q_MEDIASERVICE_CAMERA) { + m_captureSession = new QGstreamerCaptureSession(QGstreamerCaptureSession::AudioAndVideo, this); + m_cameraControl = new QGstreamerCameraControl(m_captureSession); + m_videoInput = new QGstreamerV4L2Input(this); + m_captureSession->setVideoInput(m_videoInput); + m_videoInputDevice = new QGstreamerVideoInputDeviceControl(this); + + connect(m_videoInputDevice, SIGNAL(selectedDeviceChanged(QString)), + m_videoInput, SLOT(setDevice(QString))); + + if (m_videoInputDevice->deviceCount()) + m_videoInput->setDevice(m_videoInputDevice->deviceName(m_videoInputDevice->selectedDevice())); + + m_videoRenderer = new QGstreamerVideoRenderer(this); + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + m_videoWindow = new QGstreamerVideoOverlay(this); + m_videoWidgetControl = new QGstreamerVideoWidgetControl(this); +#endif + m_imageCaptureControl = new QGstreamerImageCaptureControl(m_captureSession); + } + + m_audioInputEndpointSelector = new QGstreamerAudioInputEndpointSelector(this); + connect(m_audioInputEndpointSelector, SIGNAL(activeEndpointChanged(QString)), m_captureSession, SLOT(setCaptureDevice(QString))); + + if (m_captureSession && m_audioInputEndpointSelector->availableEndpoints().size() > 0) + m_captureSession->setCaptureDevice(m_audioInputEndpointSelector->defaultEndpoint()); + + m_metaDataControl = new QGstreamerCaptureMetaDataControl(this); + connect(m_metaDataControl, SIGNAL(metaDataChanged(QMap<QByteArray,QVariant>)), + m_captureSession, SLOT(setMetaData(QMap<QByteArray,QVariant>))); +} + +QGstreamerCaptureService::~QGstreamerCaptureService() +{ +} + +QMediaControl *QGstreamerCaptureService::requestControl(const char *name) +{ + if (!m_captureSession) + return 0; + + if (qstrcmp(name,QAudioEndpointSelector_iid) == 0) + return m_audioInputEndpointSelector; + + if (qstrcmp(name,QVideoDeviceControl_iid) == 0) + return m_videoInputDevice; + + if (qstrcmp(name,QMediaRecorderControl_iid) == 0) + return m_captureSession->recorderControl(); + + if (qstrcmp(name,QAudioEncoderControl_iid) == 0) + return m_captureSession->audioEncodeControl(); + + if (qstrcmp(name,QVideoEncoderControl_iid) == 0) + return m_captureSession->videoEncodeControl(); + + if (qstrcmp(name,QImageEncoderControl_iid) == 0) + return m_captureSession->imageEncodeControl(); + + + if (qstrcmp(name,QMediaContainerControl_iid) == 0) + return m_captureSession->mediaContainerControl(); + + if (qstrcmp(name,QCameraControl_iid) == 0) + return m_cameraControl; + + if (qstrcmp(name,QMetaDataWriterControl_iid) == 0) + return m_metaDataControl; + + if (qstrcmp(name, QCameraImageCaptureControl_iid) == 0) + return m_imageCaptureControl; + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoRendererControl_iid) == 0) { + m_videoOutput = m_videoRenderer; + m_captureSession->setVideoPreview(m_videoRenderer); + } else if (qstrcmp(name, QVideoWindowControl_iid) == 0) { + m_videoOutput = m_videoWindow; + m_captureSession->setVideoPreview(m_videoWindow); + } else if (qstrcmp(name, QVideoWidgetControl_iid) == 0) { + m_captureSession->setVideoPreview(m_videoWidgetControl); + m_videoOutput = m_videoWidgetControl; + } + + if (m_videoOutput) + return m_videoOutput; + } + + return 0; +} + +void QGstreamerCaptureService::releaseControl(QMediaControl *control) +{ + if (control && control == m_videoOutput) { + m_videoOutput = 0; + m_captureSession->setVideoPreview(0); + } +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h new file mode 100644 index 000000000..09b275c35 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercaptureservice.h @@ -0,0 +1,96 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERCAPTURESERVICE_H +#define QGSTREAMERCAPTURESERVICE_H + +#include <qmediaservice.h> +#include <qmediacontrol.h> + +#include <gst/gst.h> +QT_BEGIN_NAMESPACE +class QAudioEndpointSelector; +class QVideoDeviceControl; +QT_END_NAMESPACE + +class QGstreamerCaptureSession; +class QGstreamerCameraControl; +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerVideoRenderer; +class QGstreamerVideoOverlay; +class QGstreamerVideoWidgetControl; +class QGstreamerElementFactory; +class QGstreamerCaptureMetaDataControl; +class QGstreamerImageCaptureControl; +class QGstreamerV4L2Input; + +class QGstreamerCaptureService : public QMediaService +{ + Q_OBJECT + +public: + QGstreamerCaptureService(const QString &service, QObject *parent = 0); + virtual ~QGstreamerCaptureService(); + + QMediaControl *requestControl(const char *name); + void releaseControl(QMediaControl *); + +private: + void setAudioPreview(GstElement*); + + QGstreamerCaptureSession *m_captureSession; + QGstreamerCameraControl *m_cameraControl; + QGstreamerV4L2Input *m_videoInput; + QGstreamerCaptureMetaDataControl *m_metaDataControl; + + QAudioEndpointSelector *m_audioInputEndpointSelector; + QVideoDeviceControl *m_videoInputDevice; + + QMediaControl *m_videoOutput; + + QGstreamerVideoRenderer *m_videoRenderer; + QMediaControl *m_videoWindow; + QMediaControl *m_videoWidgetControl; + QGstreamerImageCaptureControl *m_imageCaptureControl; +}; + +#endif // QGSTREAMERCAPTURESERVICE_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp b/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp new file mode 100644 index 000000000..f8e73c7af --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp @@ -0,0 +1,1051 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamercapturesession.h" +#include "qgstreamerrecordercontrol.h" +#include "qgstreamermediacontainercontrol.h" +#include "qgstreamervideorendererinterface.h" +#include "qgstreameraudioencode.h" +#include "qgstreamervideoencode.h" +#include "qgstreamerimageencode.h" +#include "qgstreamerbushelper.h" +#include <qmediarecorder.h> + +#include <gst/gsttagsetter.h> +#include <gst/gstversion.h> + +#include <QtCore/qdebug.h> +#include <QtCore/qurl.h> +#include <QtCore/qset.h> +#include <QCoreApplication> +#include <QtCore/qmetaobject.h> +#include <QtCore/qfile.h> + +#include <QtGui/qimage.h> + +#define gstRef(element) { gst_object_ref(GST_OBJECT(element)); gst_object_sink(GST_OBJECT(element)); } +#define gstUnref(element) { if (element) { gst_object_unref(GST_OBJECT(element)); element = 0; } } + +QGstreamerCaptureSession::QGstreamerCaptureSession(QGstreamerCaptureSession::CaptureMode captureMode, QObject *parent) + :QObject(parent), + m_state(StoppedState), + m_pendingState(StoppedState), + m_waitingForEos(false), + m_pipelineMode(EmptyPipeline), + m_captureMode(captureMode), + m_audioInputFactory(0), + m_audioPreviewFactory(0), + m_videoInputFactory(0), + m_viewfinder(0), + m_viewfinderInterface(0), + m_audioSrc(0), + m_audioTee(0), + m_audioPreviewQueue(0), + m_audioPreview(0), + m_audioVolume(0), + m_muted(false), + m_videoSrc(0), + m_videoTee(0), + m_videoPreviewQueue(0), + m_videoPreview(0), + m_imageCaptureBin(0), + m_encodeBin(0), + m_passImage(false), + m_passPrerollImage(false) +{ + m_pipeline = gst_pipeline_new("media-capture-pipeline"); + gstRef(m_pipeline); + + m_bus = gst_element_get_bus(m_pipeline); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installSyncEventFilter(this); + connect(m_busHelper, SIGNAL(message(QGstreamerMessage)), SLOT(busMessage(QGstreamerMessage))); + m_audioEncodeControl = new QGstreamerAudioEncode(this); + m_videoEncodeControl = new QGstreamerVideoEncode(this); + m_imageEncodeControl = new QGstreamerImageEncode(this); + m_recorderControl = new QGstreamerRecorderControl(this); + m_mediaContainerControl = new QGstreamerMediaContainerControl(this); + + setState(StoppedState); +} + +QGstreamerCaptureSession::~QGstreamerCaptureSession() +{ + setState(StoppedState); + gst_element_set_state(m_pipeline, GST_STATE_NULL); + gst_object_unref(GST_OBJECT(m_pipeline)); +} + +void QGstreamerCaptureSession::setCaptureMode(CaptureMode mode) +{ + m_captureMode = mode; +} + +GstElement *QGstreamerCaptureSession::buildEncodeBin() +{ + GstElement *encodeBin = gst_bin_new("encode-bin"); + + GstElement *muxer = gst_element_factory_make( m_mediaContainerControl->formatElementName().constData(), "muxer"); + if (!muxer) { + qWarning() << "Could not create a media muxer element:" << m_mediaContainerControl->formatElementName(); + gst_object_unref(encodeBin); + return 0; + } + + GstElement *fileSink = gst_element_factory_make("filesink", "filesink"); + g_object_set(G_OBJECT(fileSink), "location", m_sink.toString().toLocal8Bit().constData(), NULL); + gst_bin_add_many(GST_BIN(encodeBin), muxer, fileSink, NULL); + + if (!gst_element_link(muxer, fileSink)) { + gst_object_unref(encodeBin); + return 0; + } + + if (m_captureMode & Audio) { + GstElement *audioConvert = gst_element_factory_make("audioconvert", "audioconvert"); + GstElement *audioQueue = gst_element_factory_make("queue", "audio-encode-queue"); + m_audioVolume = gst_element_factory_make("volume", "volume"); + gst_bin_add_many(GST_BIN(encodeBin), audioConvert, audioQueue, m_audioVolume, NULL); + + GstElement *audioEncoder = m_audioEncodeControl->createEncoder(); + if (!audioEncoder) { + gst_object_unref(encodeBin); + qWarning() << "Could not create an audio encoder element:" << m_audioEncodeControl->audioSettings().codec(); + return 0; + } + + gst_bin_add(GST_BIN(encodeBin), audioEncoder); + + if (!gst_element_link_many(audioConvert, audioQueue, m_audioVolume, audioEncoder, muxer, NULL)) { + gst_object_unref(encodeBin); + return 0; + } + + g_object_set(G_OBJECT(m_audioVolume), "volume", (m_muted ? 0.0 : 1.0), NULL); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(audioConvert, "sink"); + gst_element_add_pad(GST_ELEMENT(encodeBin), gst_ghost_pad_new("audiosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + } + + if (m_captureMode & Video) { + GstElement *videoQueue = gst_element_factory_make("queue", "video-encode-queue"); + GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-encoder"); + GstElement *videoscale = gst_element_factory_make("videoscale","videoscale-encoder"); + gst_bin_add_many(GST_BIN(encodeBin), videoQueue, colorspace, videoscale, NULL); + + GstElement *videoEncoder = m_videoEncodeControl->createEncoder(); + if (!videoEncoder) { + gst_object_unref(encodeBin); + qWarning() << "Could not create a video encoder element:" << m_videoEncodeControl->videoSettings().codec(); + return 0; + } + + gst_bin_add(GST_BIN(encodeBin), videoEncoder); + + if (!gst_element_link_many(videoQueue, colorspace, videoscale, videoEncoder, muxer, NULL)) { + gst_object_unref(encodeBin); + return 0; + } + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(videoQueue, "sink"); + gst_element_add_pad(GST_ELEMENT(encodeBin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + } + + return encodeBin; +} + +GstElement *QGstreamerCaptureSession::buildAudioSrc() +{ + GstElement *audioSrc = 0; + if (m_audioInputFactory) + audioSrc = m_audioInputFactory->buildElement(); + else { + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + audioSrc = gst_element_factory_make("pulsesrc", "audio_src"); +#elif defined(QT_QWS_N810) + audioSrc = gst_element_factory_make("dsppcmsrc", "audio_src"); +#else + QString elementName = "alsasrc"; + QString device; + + if (m_captureDevice.startsWith("alsa:")) { + device = m_captureDevice.mid(QString("alsa:").length()); + } else if (m_captureDevice.startsWith("oss:")) { + elementName = "osssrc"; + device = m_captureDevice.mid(QString("oss:").length()); + } else if (m_captureDevice.startsWith("pulseaudio:")) { + elementName = "pulsesrc"; + } else { + elementName = "autoaudiosrc"; + } + + audioSrc = gst_element_factory_make(elementName.toAscii().constData(), "audio_src"); + if (audioSrc && !device.isEmpty()) + g_object_set(G_OBJECT(audioSrc), "device", device.toLocal8Bit().constData(), NULL); +#endif + } + + if (!audioSrc) { + emit error(int(QMediaRecorder::ResourceError), tr("Could not create an audio source element")); + audioSrc = gst_element_factory_make("fakesrc", NULL); + } + + return audioSrc; +} + +GstElement *QGstreamerCaptureSession::buildAudioPreview() +{ + GstElement *previewElement = 0; + + if (m_audioPreviewFactory) { + previewElement = m_audioPreviewFactory->buildElement(); + } else { + + +#if 1 + previewElement = gst_element_factory_make("fakesink", "audio-preview"); +#else + GstElement *bin = gst_bin_new("audio-preview-bin"); + GstElement *visual = gst_element_factory_make("libvisual_lv_scope", "audio-preview"); + GstElement *sink = gst_element_factory_make("ximagesink", NULL); + gst_bin_add_many(GST_BIN(bin), visual, sink, NULL); + gst_element_link_many(visual,sink, NULL); + + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(visual, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("audiosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; +#endif + } + + return previewElement; +} + +GstElement *QGstreamerCaptureSession::buildVideoSrc() +{ + GstElement *videoSrc = 0; + if (m_videoInputFactory) { + videoSrc = m_videoInputFactory->buildElement(); + } else { + videoSrc = gst_element_factory_make("videotestsrc", "video_test_src"); + //videoSrc = gst_element_factory_make("v4l2src", "video_test_src"); + } + + return videoSrc; +} + +GstElement *QGstreamerCaptureSession::buildVideoPreview() +{ + GstElement *previewElement = 0; + + if (m_viewfinderInterface) { + GstElement *bin = gst_bin_new("video-preview-bin"); + GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-preview"); + GstElement *capsFilter = gst_element_factory_make("capsfilter", "capsfilter-video-preview"); + GstElement *preview = m_viewfinderInterface->videoSink(); + + gst_bin_add_many(GST_BIN(bin), colorspace, capsFilter, preview, NULL); + gst_element_link(colorspace,capsFilter); + gst_element_link(capsFilter,preview); + + QSize resolution; + qreal frameRate = 0; + + if (m_captureMode & Video) { + QVideoEncoderSettings videoSettings = m_videoEncodeControl->videoSettings(); + resolution = videoSettings.resolution(); + frameRate = videoSettings.frameRate(); + } else if (m_captureMode & Image) { + 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"; + + foreach(const QString &structureType, structureTypes) { + GstStructure *structure = gst_structure_new(structureType.toAscii().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); + + } + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(colorspace, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; + } else { +#if 1 + 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 *preview = gst_element_factory_make("ximagesink", "video-preview"); + gst_bin_add_many(GST_BIN(bin), colorspace, preview, NULL); + gst_element_link(colorspace,preview); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(colorspace, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("videosink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + previewElement = bin; +#endif + } + + return previewElement; +} + + +static gboolean passImageFilter(GstElement *element, + GstBuffer *buffer, + void *appdata) +{ + Q_UNUSED(element); + Q_UNUSED(buffer); + + QGstreamerCaptureSession *session = (QGstreamerCaptureSession *)appdata; + if (session->m_passImage || session->m_passPrerollImage) { + session->m_passImage = 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 int exposedSignalIndex = session->metaObject()->indexOfSignal("imageExposed(int)"); + session->metaObject()->method(exposedSignalIndex).invoke(session, + Qt::QueuedConnection, + Q_ARG(int,session->m_imageRequestId)); + + static int capturedSignalIndex = session->metaObject()->indexOfSignal("imageCaptured(int,QImage)"); + session->metaObject()->method(capturedSignalIndex).invoke(session, + Qt::QueuedConnection, + Q_ARG(int,session->m_imageRequestId), + Q_ARG(QImage,img)); + + return TRUE; + } else { + return FALSE; + } +} + +static gboolean saveImageFilter(GstElement *element, + GstBuffer *buffer, + GstPad *pad, + void *appdata) +{ + Q_UNUSED(element); + Q_UNUSED(pad); + QGstreamerCaptureSession *session = (QGstreamerCaptureSession *)appdata; + + QString fileName = session->m_imageFileName; + + if (!fileName.isEmpty()) { + QFile f(fileName); + if (f.open(QFile::WriteOnly)) { + f.write((const char *)buffer->data, buffer->size); + f.close(); + + static int signalIndex = session->metaObject()->indexOfSignal("imageSaved(int,QString)"); + session->metaObject()->method(signalIndex).invoke(session, + Qt::QueuedConnection, + Q_ARG(int,session->m_imageRequestId), + Q_ARG(QString,fileName)); + } + } + + return TRUE; +} + +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 *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); + + g_object_set(G_OBJECT(sink), "signal-handoffs", TRUE, NULL); + 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); + + // add ghostpads + pad = gst_element_get_static_pad(queue, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("imagesink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + m_passImage = false; + m_passPrerollImage = true; + m_imageFileName = QString(); + + return bin; +} + +void QGstreamerCaptureSession::captureImage(int requestId, const QString &fileName) +{ + m_imageRequestId = requestId; + m_imageFileName = fileName; + m_passImage = true; +} + + +#define REMOVE_ELEMENT(element) { if (element) {gst_bin_remove(GST_BIN(m_pipeline), element); element = 0;} } + +bool QGstreamerCaptureSession::rebuildGraph(QGstreamerCaptureSession::PipelineMode newMode) +{ + REMOVE_ELEMENT(m_audioSrc); + REMOVE_ELEMENT(m_audioPreview); + REMOVE_ELEMENT(m_audioPreviewQueue); + REMOVE_ELEMENT(m_audioTee); + REMOVE_ELEMENT(m_videoSrc); + REMOVE_ELEMENT(m_videoPreview); + REMOVE_ELEMENT(m_videoPreviewQueue); + REMOVE_ELEMENT(m_videoTee); + REMOVE_ELEMENT(m_encodeBin); + REMOVE_ELEMENT(m_imageCaptureBin); + m_audioVolume = 0; + + bool ok = true; + + switch (newMode) { + case EmptyPipeline: + break; + case PreviewPipeline: + if (m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + m_audioPreview = buildAudioPreview(); + + ok &= m_audioSrc && m_audioPreview; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_audioSrc, m_audioPreview, NULL); + ok &= gst_element_link(m_audioSrc, m_audioPreview); + } + } + if (m_captureMode & Video || m_captureMode & Image) { + m_videoSrc = buildVideoSrc(); + m_videoTee = gst_element_factory_make("tee", "video-preview-tee"); + m_videoPreviewQueue = gst_element_factory_make("queue", "video-preview-queue"); + m_videoPreview = buildVideoPreview(); + m_imageCaptureBin = buildImageCapture(); + + ok &= m_videoSrc && m_videoTee && m_videoPreviewQueue && m_videoPreview && m_imageCaptureBin; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_videoSrc, m_videoTee, + m_videoPreviewQueue, m_videoPreview, + m_imageCaptureBin, NULL); + + ok &= gst_element_link(m_videoSrc, m_videoTee); + ok &= gst_element_link(m_videoTee, m_videoPreviewQueue); + ok &= gst_element_link(m_videoPreviewQueue, m_videoPreview); + ok &= gst_element_link(m_videoTee, m_imageCaptureBin); + } + } + break; + case RecordingPipeline: + m_encodeBin = buildEncodeBin(); + gst_bin_add(GST_BIN(m_pipeline), m_encodeBin); + + if (m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + ok &= m_audioSrc != 0; + + gst_bin_add(GST_BIN(m_pipeline), m_audioSrc); + ok &= gst_element_link(m_audioSrc, m_encodeBin); + } + + if (m_captureMode & Video) { + m_videoSrc = buildVideoSrc(); + ok &= m_videoSrc != 0; + + gst_bin_add(GST_BIN(m_pipeline), m_videoSrc); + ok &= gst_element_link(m_videoSrc, m_encodeBin); + } + + if (!m_metaData.isEmpty()) + setMetaData(m_metaData); + + break; + case PreviewAndRecordingPipeline: + m_encodeBin = buildEncodeBin(); + if (m_encodeBin) + gst_bin_add(GST_BIN(m_pipeline), m_encodeBin); + + ok &= m_encodeBin != 0; + + if (ok && m_captureMode & Audio) { + m_audioSrc = buildAudioSrc(); + m_audioPreview = buildAudioPreview(); + m_audioTee = gst_element_factory_make("tee", NULL); + m_audioPreviewQueue = gst_element_factory_make("queue", NULL); + + ok &= m_audioSrc && m_audioPreview && m_audioTee && m_audioPreviewQueue; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_audioSrc, m_audioTee, + m_audioPreviewQueue, m_audioPreview, NULL); + ok &= gst_element_link(m_audioSrc, m_audioTee); + ok &= gst_element_link(m_audioTee, m_audioPreviewQueue); + ok &= gst_element_link(m_audioPreviewQueue, m_audioPreview); + ok &= gst_element_link(m_audioTee, m_encodeBin); + } + } + + if (ok && (m_captureMode & Video || m_captureMode & Image)) { + m_videoSrc = buildVideoSrc(); + m_videoPreview = buildVideoPreview(); + m_videoTee = gst_element_factory_make("tee", NULL); + m_videoPreviewQueue = gst_element_factory_make("queue", NULL); + + ok &= m_videoSrc && m_videoPreview && m_videoTee && m_videoPreviewQueue; + + if (ok) { + gst_bin_add_many(GST_BIN(m_pipeline), m_videoSrc, m_videoTee, + m_videoPreviewQueue, m_videoPreview, NULL); + ok &= gst_element_link(m_videoSrc, m_videoTee); + ok &= gst_element_link(m_videoTee, m_videoPreviewQueue); + ok &= gst_element_link(m_videoPreviewQueue, m_videoPreview); + } + + if (ok && (m_captureMode & Video)) + ok &= gst_element_link(m_videoTee, m_encodeBin); + } + + if (!m_metaData.isEmpty()) + setMetaData(m_metaData); + + + break; + } + + if (!ok) { + emit error(int(QMediaRecorder::FormatError),tr("Failed to build media capture pipeline.")); + } + + dumpGraph( QString("rebuild_graph_%1_%2").arg(m_pipelineMode).arg(newMode) ); + if (m_encodeBin) { + QString fileName = QString("rebuild_graph_encode_%1_%2").arg(m_pipelineMode).arg(newMode); +#if !(GST_DISABLE_GST_DEBUG) && (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + _gst_debug_bin_to_dot_file(GST_BIN(m_encodeBin), GST_DEBUG_GRAPH_SHOW_ALL, fileName.toAscii()); +#endif + } + + if (ok) { + m_pipelineMode = newMode; + } else { + m_pipelineMode = EmptyPipeline; + + REMOVE_ELEMENT(m_audioSrc); + REMOVE_ELEMENT(m_audioPreview); + REMOVE_ELEMENT(m_audioPreviewQueue); + REMOVE_ELEMENT(m_audioTee); + REMOVE_ELEMENT(m_videoSrc); + REMOVE_ELEMENT(m_videoPreview); + REMOVE_ELEMENT(m_videoPreviewQueue); + REMOVE_ELEMENT(m_videoTee); + REMOVE_ELEMENT(m_encodeBin); + } + + return ok; +} + +void QGstreamerCaptureSession::dumpGraph(const QString &fileName) +{ +#if !(GST_DISABLE_GST_DEBUG) && (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + _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.toAscii()); +#endif +} + +QUrl QGstreamerCaptureSession::outputLocation() const +{ + return m_sink; +} + +bool QGstreamerCaptureSession::setOutputLocation(const QUrl& sink) +{ + m_sink = sink; + return true; +} + +void QGstreamerCaptureSession::setAudioInput(QGstreamerElementFactory *audioInput) +{ + m_audioInputFactory = audioInput; +} + +void QGstreamerCaptureSession::setAudioPreview(QGstreamerElementFactory *audioPreview) +{ + m_audioPreviewFactory = audioPreview; +} + +void QGstreamerCaptureSession::setVideoInput(QGstreamerVideoInput *videoInput) +{ + m_videoInputFactory = videoInput; +} + +void QGstreamerCaptureSession::setVideoPreview(QObject *viewfinder) +{ + m_viewfinderInterface = qobject_cast<QGstreamerVideoRendererInterface*>(viewfinder); + if (!m_viewfinderInterface) + viewfinder = 0; + + if (m_viewfinder != viewfinder) { + bool oldReady = isReady(); + + if (m_viewfinder) { + disconnect(m_viewfinder, SIGNAL(sinkChanged()), + this, SIGNAL(viewfinderChanged())); + disconnect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + } + + m_viewfinder = viewfinder; + //m_viewfinderHasChanged = true; + + if (m_viewfinder) { + connect(m_viewfinder, SIGNAL(sinkChanged()), + this, SIGNAL(viewfinderChanged())); + connect(m_viewfinder, SIGNAL(readyChanged(bool)), + this, SIGNAL(readyChanged(bool))); + } + + emit viewfinderChanged(); + if (oldReady != isReady()) + emit readyChanged(isReady()); + } +} + +bool QGstreamerCaptureSession::isReady() const +{ + return m_viewfinderInterface != 0 && m_viewfinderInterface->isReady(); +} + +QGstreamerCaptureSession::State QGstreamerCaptureSession::state() const +{ + return m_state; +} + +void QGstreamerCaptureSession::waitForStopped() +{ + GstState state = GST_STATE_PLAYING; + gst_element_get_state(m_pipeline, &state, 0, 0); + + while (state != GST_STATE_NULL) { + qApp->processEvents(); + gst_element_get_state(m_pipeline, &state, 0, 0); + } +} + +void QGstreamerCaptureSession::setState(QGstreamerCaptureSession::State newState) +{ + if (newState == m_pendingState && !m_waitingForEos) + return; + + m_pendingState = newState; + + PipelineMode newMode = EmptyPipeline; + + switch (newState) { + case PausedState: + case RecordingState: + newMode = PreviewAndRecordingPipeline; + break; + case PreviewState: + newMode = PreviewPipeline; + break; + case StoppedState: + newMode = EmptyPipeline; + break; + } + + if (newMode != m_pipelineMode) { + if (m_pipelineMode == PreviewAndRecordingPipeline) { + if (!m_waitingForEos) { + m_waitingForEos = true; + //qDebug() << "Waiting for EOS"; + //with live sources it's necessary to send EOS even to pipeline + //before going to STOPPED state + gst_element_send_event(m_pipeline, gst_event_new_eos()); + // Unless gstreamer is in GST_STATE_PLAYING our EOS message will not be received. + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + + return; + } else { + m_waitingForEos = false; + //qDebug() << "EOS received"; + } + } + + //select suitable default codecs/containers, if necessary + m_recorderControl->applySettings(); + + gst_element_set_state(m_pipeline, GST_STATE_NULL); + + //It would be better to do this async. but + //gstreamer doesn't notify about pipeline went to NULL state + waitForStopped(); + if (!rebuildGraph(newMode)) { + m_pendingState = StoppedState; + m_state = StoppedState; + emit stateChanged(StoppedState); + + return; + } + } + + switch (newState) { + case PausedState: + gst_element_set_state(m_pipeline, GST_STATE_PAUSED); + break; + case RecordingState: + case PreviewState: + gst_element_set_state(m_pipeline, GST_STATE_PLAYING); + break; + case StoppedState: + gst_element_set_state(m_pipeline, GST_STATE_NULL); + } + + //we have to do it here, since gstreamer will not emit bus messages any more + if (newState == StoppedState) { + m_state = StoppedState; + emit stateChanged(StoppedState); + } +} + + +qint64 QGstreamerCaptureSession::duration() const +{ + GstFormat format = GST_FORMAT_TIME; + gint64 duration = 0; + + if ( m_encodeBin && gst_element_query_position(m_encodeBin, &format, &duration)) + return duration / 1000000; + else + return 0; +} + +void QGstreamerCaptureSession::setCaptureDevice(const QString &deviceName) +{ + m_captureDevice = deviceName; +} + +void QGstreamerCaptureSession::setMetaData(const QMap<QByteArray, QVariant> &data) +{ + //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; + } + + } + + } + } +} + +bool QGstreamerCaptureSession::processSyncMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm && GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) { + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoPreview)) + m_viewfinderInterface->handleSyncMessage(gm); + + if (gst_structure_has_name(gm->structure, "prepare-xwindow-id")) { + if (m_audioPreviewFactory) + m_audioPreviewFactory->prepareWinId(); + + if (m_viewfinderInterface) + m_viewfinderInterface->precessNewStream(); + + return true; + } + } + + return false; +} + +void QGstreamerCaptureSession::busMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error (gm, &err, &debug); + emit error(int(QMediaRecorder::ResourceError),QString::fromUtf8(err->message)); + g_error_free (err); + g_free (debug); + } + + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_pipeline)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_DURATION: + break; + + case GST_MESSAGE_EOS: + if (m_waitingForEos) + setState(m_pendingState); + break; + + case GST_MESSAGE_STATE_CHANGED: + { + + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + /* + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); + + #define ENUM_NAME(c,e,v) (c::staticMetaObject.enumerator(c::staticMetaObject.indexOfEnumerator(e)).valueToKey((v))) + + qDebug() << "Current session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_state); + qDebug() << "Pending session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_pendingState); + */ + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + case GST_STATE_READY: + if (m_state != StoppedState && m_pendingState == StoppedState) { + emit stateChanged(m_state = StoppedState); + dumpGraph("stopped"); + } + break; + case GST_STATE_PAUSED: + if (m_state != PausedState && m_pendingState == PausedState) + emit stateChanged(m_state = PausedState); + dumpGraph("paused"); + + if (m_pipelineMode == RecordingPipeline && !m_metaData.isEmpty()) + setMetaData(m_metaData); + break; + case GST_STATE_PLAYING: + { + if ((m_pendingState == PreviewState || m_pendingState == RecordingState) && + m_state != m_pendingState) + { + m_state = m_pendingState; + emit stateChanged(m_state); + } + + if (m_pipelineMode == PreviewPipeline) + dumpGraph("preview"); + else + dumpGraph("recording"); + } + break; + } + } + break; + default: + break; + } + //qDebug() << "New session state:" << ENUM_NAME(QGstreamerCaptureSession,"State",m_state); + } + + if (m_videoPreview && m_viewfinderInterface && + GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoPreview)) + m_viewfinderInterface->handleBusMessage(gm); + } +} + +void QGstreamerCaptureSession::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + if (m_audioVolume) + g_object_set(G_OBJECT(m_audioVolume), "volume", (m_muted ? 0.0 : 1.0), NULL); + emit mutedChanged(muted); + } +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.h b/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.h new file mode 100644 index 000000000..f04a49be2 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.h @@ -0,0 +1,211 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERCAPTURESESSION_H +#define QGSTREAMERCAPTURESESSION_H + +#include <qmediarecordercontrol.h> +#include <qmediarecorder.h> + +#include <QtCore/qurl.h> + +#include <gst/gst.h> + +#include "qgstreamerbushelper.h" + +QT_USE_NAMESPACE + +class QGstreamerMessage; +class QGstreamerBusHelper; +class QGstreamerAudioEncode; +class QGstreamerVideoEncode; +class QGstreamerImageEncode; +class QGstreamerRecorderControl; +class QGstreamerMediaContainerControl; +class QGstreamerVideoRendererInterface; + +class QGstreamerElementFactory +{ +public: + virtual GstElement *buildElement() = 0; + virtual void prepareWinId() {} +}; + +class QGstreamerVideoInput : public QGstreamerElementFactory +{ +public: + virtual QList<qreal> supportedFrameRates(const QSize &frameSize = QSize()) const = 0; + virtual QList<QSize> supportedResolutions(qreal frameRate = -1) const = 0; +}; + +class QGstreamerCaptureSession : public QObject, public QGstreamerSyncEventFilter +{ + Q_OBJECT + Q_PROPERTY(qint64 duration READ duration NOTIFY durationChanged) + Q_ENUMS(State) + Q_ENUMS(CaptureMode) +public: + enum CaptureMode { Audio = 1, Video = 2, Image=4, AudioAndVideo = Audio | Video }; + enum State { StoppedState, PreviewState, PausedState, RecordingState }; + + QGstreamerCaptureSession(CaptureMode captureMode, QObject *parent); + ~QGstreamerCaptureSession(); + + CaptureMode captureMode() const { return m_captureMode; } + void setCaptureMode(CaptureMode); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl& sink); + + QGstreamerAudioEncode *audioEncodeControl() const { return m_audioEncodeControl; } + QGstreamerVideoEncode *videoEncodeControl() const { return m_videoEncodeControl; } + QGstreamerImageEncode *imageEncodeControl() const { return m_imageEncodeControl; } + + QGstreamerRecorderControl *recorderControl() const { return m_recorderControl; } + QGstreamerMediaContainerControl *mediaContainerControl() const { return m_mediaContainerControl; } + + QGstreamerElementFactory *audioInput() const { return m_audioInputFactory; } + void setAudioInput(QGstreamerElementFactory *audioInput); + + QGstreamerElementFactory *audioPreview() const { return m_audioPreviewFactory; } + void setAudioPreview(QGstreamerElementFactory *audioPreview); + + QGstreamerVideoInput *videoInput() const { return m_videoInputFactory; } + void setVideoInput(QGstreamerVideoInput *videoInput); + + QObject *videoPreview() const { return m_viewfinder; } + void setVideoPreview(QObject *viewfinder); + + void captureImage(int requestId, const QString &fileName); + + State state() const; + qint64 duration() const; + bool isMuted() const { return m_muted; } + + bool isReady() const; + + bool processSyncMessage(const QGstreamerMessage &message); + +signals: + void stateChanged(QGstreamerCaptureSession::State state); + void durationChanged(qint64 duration); + void error(int error, const QString &errorString); + void imageExposed(int requestId); + void imageCaptured(int requestId, const QImage &img); + void imageSaved(int requestId, const QString &path); + void mutedChanged(bool); + void readyChanged(bool); + void viewfinderChanged(); + +public slots: + void setState(QGstreamerCaptureSession::State); + void setCaptureDevice(const QString &deviceName); + + void dumpGraph(const QString &fileName); + + void setMetaData(const QMap<QByteArray, QVariant>&); + void setMuted(bool); + +private slots: + void busMessage(const QGstreamerMessage &message); + +private: + enum PipelineMode { EmptyPipeline, PreviewPipeline, RecordingPipeline, PreviewAndRecordingPipeline }; + + GstElement *buildEncodeBin(); + GstElement *buildAudioSrc(); + GstElement *buildAudioPreview(); + GstElement *buildVideoSrc(); + GstElement *buildVideoPreview(); + GstElement *buildImageCapture(); + + void waitForStopped(); + bool rebuildGraph(QGstreamerCaptureSession::PipelineMode newMode); + + QUrl m_sink; + QString m_captureDevice; + State m_state; + State m_pendingState; + bool m_waitingForEos; + PipelineMode m_pipelineMode; + QGstreamerCaptureSession::CaptureMode m_captureMode; + QMap<QByteArray, QVariant> m_metaData; + + QGstreamerElementFactory *m_audioInputFactory; + QGstreamerElementFactory *m_audioPreviewFactory; + QGstreamerVideoInput *m_videoInputFactory; + QObject *m_viewfinder; + QGstreamerVideoRendererInterface *m_viewfinderInterface; + + QGstreamerAudioEncode *m_audioEncodeControl; + QGstreamerVideoEncode *m_videoEncodeControl; + QGstreamerImageEncode *m_imageEncodeControl; + QGstreamerRecorderControl *m_recorderControl; + QGstreamerMediaContainerControl *m_mediaContainerControl; + + QGstreamerBusHelper *m_busHelper; + GstBus* m_bus; + GstElement *m_pipeline; + + GstElement *m_audioSrc; + GstElement *m_audioTee; + GstElement *m_audioPreviewQueue; + GstElement *m_audioPreview; + GstElement *m_audioVolume; + bool m_muted; + + GstElement *m_videoSrc; + GstElement *m_videoTee; + GstElement *m_videoPreviewQueue; + GstElement *m_videoPreview; + + GstElement *m_imageCaptureBin; + + GstElement *m_encodeBin; + +public: + bool m_passImage; + bool m_passPrerollImage; + QString m_imageFileName; + int m_imageRequestId; +}; + +#endif // QGSTREAMERCAPTURESESSION_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp b/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp new file mode 100644 index 000000000..99c3b1665 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerimagecapturecontrol.h" +#include <QtCore/QDebug> +#include <QtCore/QDir> + +QGstreamerImageCaptureControl::QGstreamerImageCaptureControl(QGstreamerCaptureSession *session) + :QCameraImageCaptureControl(session), m_session(session), m_ready(false), m_lastId(0) +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), SLOT(updateState())); + connect(m_session, SIGNAL(imageExposed(int)), this, SIGNAL(imageExposed(int))); + connect(m_session, SIGNAL(imageCaptured(int,QImage)), this, SIGNAL(imageCaptured(int,QImage))); + connect(m_session, SIGNAL(imageSaved(int,QString)), this, SIGNAL(imageSaved(int,QString))); +} + +QGstreamerImageCaptureControl::~QGstreamerImageCaptureControl() +{ +} + +bool QGstreamerImageCaptureControl::isReadyForCapture() const +{ + return m_ready; +} + +int QGstreamerImageCaptureControl::capture(const QString &fileName) +{ + QString path = fileName; + if (path.isEmpty()) { + int lastImage = 0; + QDir outputDir = QDir::currentPath(); + foreach(QString fileName, outputDir.entryList(QStringList() << "img_*.jpg")) { + int imgNumber = fileName.mid(4, fileName.size()-8).toInt(); + lastImage = qMax(lastImage, imgNumber); + } + + path = QString("img_%1.jpg").arg(lastImage+1, + 4, //fieldWidth + 10, + QLatin1Char('0')); + } + m_lastId++; + + m_session->captureImage(m_lastId, path); + + return m_lastId; +} + +void QGstreamerImageCaptureControl::cancelCapture() +{ + +} + +void QGstreamerImageCaptureControl::updateState() +{ + bool ready = m_session->state() == QGstreamerCaptureSession::PreviewState; + if (m_ready != ready) { + emit readyForCaptureChanged(m_ready = ready); + } +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.h b/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.h new file mode 100644 index 000000000..898162440 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerimagecapturecontrol.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERIMAGECAPTURECONTROL_H +#define QGSTREAMERIMAGECAPTURECONTROL_H + +#include <qcameraimagecapturecontrol.h> +#include "qgstreamercapturesession.h" +QT_USE_NAMESPACE + +class QGstreamerImageCaptureControl : public QCameraImageCaptureControl +{ + Q_OBJECT +public: + QGstreamerImageCaptureControl(QGstreamerCaptureSession *session); + virtual ~QGstreamerImageCaptureControl(); + + QCameraImageCapture::DriveMode driveMode() const { return QCameraImageCapture::SingleImageCapture; } + void setDriveMode(QCameraImageCapture::DriveMode) {} + + bool isReadyForCapture() const; + int capture(const QString &fileName); + void cancelCapture(); + +private slots: + void updateState(); + +private: + QGstreamerCaptureSession *m_session; + bool m_ready; + int m_lastId; +}; + +#endif // QGSTREAMERCAPTURECORNTROL_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.cpp b/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.cpp new file mode 100644 index 000000000..5aef97794 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.cpp @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerimageencode.h" +#include "qgstreamercapturesession.h" + +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerImageEncode::QGstreamerImageEncode(QGstreamerCaptureSession *session) + :QImageEncoderControl(session), m_session(session) +{ +} + +QGstreamerImageEncode::~QGstreamerImageEncode() +{ +} + +QList<QSize> QGstreamerImageEncode::supportedResolutions(const QImageEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = m_session->videoInput() != 0; + + return m_session->videoInput() ? m_session->videoInput()->supportedResolutions() : QList<QSize>(); +} + +QStringList QGstreamerImageEncode::supportedImageCodecs() const +{ + return QStringList() << "jpeg"; +} + +QString QGstreamerImageEncode::imageCodecDescription(const QString &codecName) const +{ + if (codecName == "jpeg") + return tr("JPEG image encoder"); + + return QString(); +} + +QImageEncoderSettings QGstreamerImageEncode::imageSettings() const +{ + return m_settings; +} + +void QGstreamerImageEncode::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_settings != settings) { + m_settings = settings; + emit settingsChanged(); + } +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.h b/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.h new file mode 100644 index 000000000..b66ba75f8 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerimageencode.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERIMAGEENCODE_H +#define QGSTREAMERIMAGEENCODE_H + +class QGstreamerCaptureSession; + +#include <qimageencodercontrol.h> + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> + +#include <gst/gst.h> +QT_USE_NAMESPACE + +class QGstreamerImageEncode : public QImageEncoderControl +{ + Q_OBJECT +public: + QGstreamerImageEncode(QGstreamerCaptureSession *session); + virtual ~QGstreamerImageEncode(); + + QList<QSize> supportedResolutions(const QImageEncoderSettings &settings = QImageEncoderSettings(), + bool *continuous = 0) const; + + QStringList supportedImageCodecs() const; + QString imageCodecDescription(const QString &codecName) const; + + QImageEncoderSettings imageSettings() const; + void setImageSettings(const QImageEncoderSettings &settings); + +Q_SIGNALS: + void settingsChanged(); + +private: + QImageEncoderSettings m_settings; + + QGstreamerCaptureSession *m_session; +}; + +#endif diff --git a/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp b/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp new file mode 100644 index 000000000..34c43e3ac --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamermediacontainercontrol.h" + + +#include <QtCore/qdebug.h> + +QGstreamerMediaContainerControl::QGstreamerMediaContainerControl(QObject *parent) + :QMediaContainerControl(parent) +{ + QList<QByteArray> formatCandidates; + formatCandidates << "matroska" << "ogg" << "mp4" << "wav" << "quicktime" << "avi" << "3gpp"; + formatCandidates << "flv" << "amr" << "asf" << "dv" << "gif"; + formatCandidates << "mpeg" << "vob" << "mpegts" << "3g2" << "3gp"; + formatCandidates << "raw"; + + m_elementNames["matroska"] = "matroskamux"; + m_elementNames["ogg"] = "oggmux"; + m_elementNames["mp4"] = "ffmux_mp4"; + m_elementNames["quicktime"] = "ffmux_mov"; + m_elementNames["avi"] = "avimux"; + m_elementNames["3gpp"] = "gppmux"; + m_elementNames["flv"] = "flvmux"; + m_elementNames["wav"] = "wavenc"; + m_elementNames["amr"] = "ffmux_amr"; + m_elementNames["asf"] = "ffmux_asf"; + m_elementNames["dv"] = "ffmux_dv"; + m_elementNames["gif"] = "ffmux_gif"; + m_elementNames["mpeg"] = "ffmux_mpeg"; + m_elementNames["vob"] = "ffmux_vob"; + m_elementNames["mpegts"] = "ffmux_mpegts"; + m_elementNames["3g2"] = "ffmux_3g2"; + m_elementNames["3gp"] = "ffmux_3gp"; + m_elementNames["raw"] = "identity"; + + m_containerExtensions["matroska"] = "mkv"; + m_containerExtensions["quicktime"] = "mov"; + m_containerExtensions["mpegts"] = "m2t"; + m_containerExtensions["mpeg"] = "mpg"; + + QSet<QString> allTypes; + + foreach( const QByteArray& formatName, formatCandidates ) { + QByteArray elementName = m_elementNames[formatName]; + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + if (factory) { + m_supportedContainers.append(formatName); + const gchar *descr = gst_element_factory_get_description(factory); + m_containerDescriptions.insert(formatName, QString::fromUtf8(descr)); + + + if (formatName == QByteArray("raw")) { + m_streamTypes.insert(formatName, allTypes); + } else { + QSet<QString> types = supportedStreamTypes(factory, GST_PAD_SINK); + m_streamTypes.insert(formatName, types); + allTypes.unite(types); + } + + gst_object_unref(GST_OBJECT(factory)); + } + } + + //if (!m_supportedContainers.isEmpty()) + // setContainerMimeType(m_supportedContainers[0]); +} + +QSet<QString> QGstreamerMediaContainerControl::supportedStreamTypes(GstElementFactory *factory, GstPadDirection direction) +{ + QSet<QString> types; + const GList *pads = gst_element_factory_get_static_pad_templates(factory); + for (const GList *pad = pads; pad; pad = g_list_next(pad)) { + GstStaticPadTemplate *templ = (GstStaticPadTemplate*)pad->data; + if (templ->direction == direction) { + GstCaps *caps = gst_static_caps_get(&templ->static_caps); + for (uint i=0; i<gst_caps_get_size(caps); i++) { + GstStructure *structure = gst_caps_get_structure(caps, i); + types.insert( QString::fromUtf8(gst_structure_get_name(structure)) ); + } + gst_caps_unref(caps); + } + } + + return types; +} + + +QSet<QString> QGstreamerMediaContainerControl::supportedStreamTypes(const QString &container) const +{ + return m_streamTypes.value(container); +} + +QString QGstreamerMediaContainerControl::containerExtension() const +{ + return m_containerExtensions.value(m_format, m_format); +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.h b/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.h new file mode 100644 index 000000000..57e0a5bf1 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamermediacontainercontrol.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERMEDIACONTAINERCONTROL_H +#define QGSTREAMERMEDIACONTAINERCONTROL_H + +#include <qmediacontainercontrol.h> +#include <QtCore/qstringlist.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +QT_USE_NAMESPACE + +class QGstreamerMediaContainerControl : public QMediaContainerControl +{ +Q_OBJECT +public: + QGstreamerMediaContainerControl(QObject *parent); + virtual ~QGstreamerMediaContainerControl() {}; + + virtual QStringList supportedContainers() const { return m_supportedContainers; } + virtual QString containerMimeType() const { return m_format; } + virtual void setContainerMimeType(const QString &formatMimeType) { m_format = formatMimeType; } + + virtual QString containerDescription(const QString &formatMimeType) const { return m_containerDescriptions.value(formatMimeType); } + + QByteArray formatElementName() const { return m_elementNames.value(containerMimeType()); } + + QSet<QString> supportedStreamTypes(const QString &container) const; + + static QSet<QString> supportedStreamTypes(GstElementFactory *factory, GstPadDirection direction); + + QString containerExtension() const; + +private: + QString m_format; + QStringList m_supportedContainers; + QMap<QString,QByteArray> m_elementNames; + QMap<QString, QString> m_containerDescriptions; + QMap<QString, QString> m_containerExtensions; + QMap<QString, QSet<QString> > m_streamTypes; +}; + +#endif // QGSTREAMERMEDIACONTAINERCONTROL_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp b/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp new file mode 100644 index 000000000..24b28dead --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.cpp @@ -0,0 +1,289 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerrecordercontrol.h" +#include "qgstreameraudioencode.h" +#include "qgstreamervideoencode.h" +#include "qgstreamermediacontainercontrol.h" +#include <QtCore/QDebug> + +QGstreamerRecorderControl::QGstreamerRecorderControl(QGstreamerCaptureSession *session) + :QMediaRecorderControl(session), m_session(session), m_state(QMediaRecorder::StoppedState) +{ + connect(m_session, SIGNAL(stateChanged(QGstreamerCaptureSession::State)), SLOT(updateState())); + connect(m_session, SIGNAL(error(int,QString)), SIGNAL(error(int,QString))); + connect(m_session, SIGNAL(durationChanged(qint64)), SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedChanged(bool)), SIGNAL(mutedChanged(bool))); + m_hasPreviewState = m_session->captureMode() != QGstreamerCaptureSession::Audio; +} + +QGstreamerRecorderControl::~QGstreamerRecorderControl() +{ +} + +QUrl QGstreamerRecorderControl::outputLocation() const +{ + return m_session->outputLocation(); +} + +bool QGstreamerRecorderControl::setOutputLocation(const QUrl &sink) +{ + m_outputLocation = sink; + m_session->setOutputLocation(sink); + return true; +} + + +QMediaRecorder::State QGstreamerRecorderControl::state() const +{ + switch ( m_session->state() ) { + case QGstreamerCaptureSession::RecordingState: + return QMediaRecorder::RecordingState; + case QGstreamerCaptureSession::PausedState: + return QMediaRecorder::PausedState; + case QGstreamerCaptureSession::PreviewState: + case QGstreamerCaptureSession::StoppedState: + return QMediaRecorder::StoppedState; + } + + return QMediaRecorder::StoppedState; + +} + +void QGstreamerRecorderControl::updateState() +{ + QMediaRecorder::State newState = state(); + if (m_state != newState) { + m_state = newState; + emit stateChanged(m_state); + } +} + +qint64 QGstreamerRecorderControl::duration() const +{ + return m_session->duration(); +} + +void QGstreamerRecorderControl::record() +{ + if (m_outputLocation.isEmpty()) { + QString container = m_session->mediaContainerControl()->containerExtension(); + if (container.isEmpty()) + container = "raw"; + + m_session->setOutputLocation(QUrl(generateFileName(defaultDir(), container))); + } + + m_session->dumpGraph("before-record"); + if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { + m_session->setState(QGstreamerCaptureSession::RecordingState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); + + m_session->dumpGraph("after-record"); +} + +void QGstreamerRecorderControl::pause() +{ + m_session->dumpGraph("before-pause"); + if (!m_hasPreviewState || m_session->state() != QGstreamerCaptureSession::StoppedState) { + m_session->setState(QGstreamerCaptureSession::PausedState); + } else + emit error(QMediaRecorder::ResourceError, tr("Service has not been started")); +} + +void QGstreamerRecorderControl::stop() +{ + if (!m_hasPreviewState) { + m_session->setState(QGstreamerCaptureSession::StoppedState); + } else { + if (m_session->state() != QGstreamerCaptureSession::StoppedState) + m_session->setState(QGstreamerCaptureSession::PreviewState); + } +} + +void QGstreamerRecorderControl::applySettings() +{ + //Check the codecs are compatible with container, + //and choose the compatible codecs/container if omitted + QGstreamerAudioEncode *audioEncodeControl = m_session->audioEncodeControl(); + QGstreamerVideoEncode *videoEncodeControl = m_session->videoEncodeControl(); + QGstreamerMediaContainerControl *mediaContainerControl = m_session->mediaContainerControl(); + + bool needAudio = m_session->captureMode() & QGstreamerCaptureSession::Audio; + bool needVideo = m_session->captureMode() & QGstreamerCaptureSession::Video; + + QStringList containerCandidates; + if (mediaContainerControl->containerMimeType().isEmpty()) + containerCandidates = mediaContainerControl->supportedContainers(); + else + containerCandidates << mediaContainerControl->containerMimeType(); + + + QStringList audioCandidates; + if (needAudio) { + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + if (audioSettings.codec().isEmpty()) + audioCandidates = audioEncodeControl->supportedAudioCodecs(); + else + audioCandidates << audioSettings.codec(); + } + + QStringList videoCandidates; + if (needVideo) { + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + if (videoSettings.codec().isEmpty()) + videoCandidates = videoEncodeControl->supportedVideoCodecs(); + else + videoCandidates << videoSettings.codec(); + } + + QString container; + QString audioCodec; + QString videoCodec; + + foreach (const QString &containerCandidate, containerCandidates) { + QSet<QString> supportedTypes = mediaContainerControl->supportedStreamTypes(containerCandidate); + + audioCodec.clear(); + videoCodec.clear(); + + if (needAudio) { + bool found = false; + foreach (const QString &audioCandidate, audioCandidates) { + QSet<QString> audioTypes = audioEncodeControl->supportedStreamTypes(audioCandidate); + if (!audioTypes.intersect(supportedTypes).isEmpty()) { + found = true; + audioCodec = audioCandidate; + break; + } + } + if (!found) + continue; + } + + if (needVideo) { + bool found = false; + foreach (const QString &videoCandidate, videoCandidates) { + QSet<QString> videoTypes = videoEncodeControl->supportedStreamTypes(videoCandidate); + if (!videoTypes.intersect(supportedTypes).isEmpty()) { + found = true; + videoCodec = videoCandidate; + break; + } + } + if (!found) + continue; + } + + container = containerCandidate; + break; + } + + if (container.isEmpty()) { + emit error(QMediaRecorder::FormatError, tr("Not compatible codecs and container format.")); + } else { + mediaContainerControl->setContainerMimeType(container); + + if (needAudio) { + QAudioEncoderSettings audioSettings = audioEncodeControl->audioSettings(); + audioSettings.setCodec(audioCodec); + audioEncodeControl->setAudioSettings(audioSettings); + } + + if (needVideo) { + QVideoEncoderSettings videoSettings = videoEncodeControl->videoSettings(); + videoSettings.setCodec(videoCodec); + videoEncodeControl->setVideoSettings(videoSettings); + } + } +} + + +bool QGstreamerRecorderControl::isMuted() const +{ + return m_session->isMuted(); +} + +void QGstreamerRecorderControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +QDir QGstreamerRecorderControl::defaultDir() const +{ + QStringList dirCandidates; + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) + dirCandidates << QLatin1String("/home/user/MyDocs"); +#endif + + dirCandidates << QDir::home().filePath("Documents"); + dirCandidates << QDir::home().filePath("My Documents"); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + foreach (const QString &path, dirCandidates) { + QDir dir(path); + if (dir.exists() && QFileInfo(path).isWritable()) + return dir; + } + + return QDir(); +} + +QString QGstreamerRecorderControl::generateFileName(const QDir &dir, const QString &ext) const +{ + + int lastClip = 0; + foreach(QString fileName, dir.entryList(QStringList() << QString("clip_*.%1").arg(ext))) { + int imgNumber = fileName.mid(5, fileName.size()-6-ext.length()).toInt(); + lastClip = qMax(lastClip, imgNumber); + } + + QString name = QString("clip_%1.%2").arg(lastClip+1, + 4, //fieldWidth + 10, + QLatin1Char('0')).arg(ext); + + return dir.absoluteFilePath(name); +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.h b/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.h new file mode 100644 index 000000000..c252f1543 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerrecordercontrol.h @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERRECORDERCONTROL_H +#define QGSTREAMERRECORDERCONTROL_H + +#include <QtCore/QDir> + +#include <qmediarecordercontrol.h> +#include "qgstreamercapturesession.h" + +QT_USE_NAMESPACE + +class QGstreamerRecorderControl : public QMediaRecorderControl +{ + Q_OBJECT + +public: + QGstreamerRecorderControl(QGstreamerCaptureSession *session); + virtual ~QGstreamerRecorderControl(); + + QUrl outputLocation() const; + bool setOutputLocation(const QUrl &sink); + + QMediaRecorder::State state() const; + + qint64 duration() const; + + bool isMuted() const; + + void applySettings(); + +public slots: + void record(); + void pause(); + void stop(); + void setMuted(bool); + +private slots: + void updateState(); + +private: + QDir defaultDir() const; + QString generateFileName(const QDir &dir, const QString &ext) const; + + QUrl m_outputLocation; + QGstreamerCaptureSession *m_session; + QMediaRecorder::State m_state; + bool m_hasPreviewState; +}; + +#endif // QGSTREAMERCAPTURECORNTROL_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.cpp b/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.cpp new file mode 100644 index 000000000..70907b2dd --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.cpp @@ -0,0 +1,297 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerv4l2input.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qfile.h> + +#include <linux/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <linux/videodev2.h> + +QT_BEGIN_NAMESPACE +static inline uint qHash(const QSize& key) { return uint(key.width()*256+key.height()); } + +static bool operator<(const QSize &s1, const QSize s2) +{ + return s1.width()*s1.height() < s2.width()*s2.height(); +} +QT_END_NAMESPACE + +QGstreamerV4L2Input::QGstreamerV4L2Input(QObject *parent) + :QObject(parent) +{ +} + +QGstreamerV4L2Input::~QGstreamerV4L2Input() +{ +} + +GstElement *QGstreamerV4L2Input::buildElement() +{ +#ifndef Q_WS_MAEMO_5 + GstElement *camera = gst_element_factory_make("v4l2src", "camera_source"); +#else + GstElement *camera = gst_element_factory_make("v4l2camsrc", "camera_source"); +#endif + if (camera && !m_device.isEmpty() ) + g_object_set(G_OBJECT(camera), "device", m_device.constData(), NULL); + + return camera; +} + +void QGstreamerV4L2Input::setDevice(const QByteArray &newDevice) +{ + if (m_device != newDevice) { + m_device = newDevice; + updateSupportedResolutions(newDevice); + } +} + +void QGstreamerV4L2Input::setDevice(const QString &device) +{ + setDevice(QFile::encodeName(device)); +} + +void QGstreamerV4L2Input::updateSupportedResolutions(const QByteArray &device) +{ + m_frameRates.clear(); + m_resolutions.clear(); + m_ratesByResolution.clear(); + + QSet<QSize> allResolutions; + QSet<int> allFrameRates; + + QFile f(device); + + if (!f.open(QFile::ReadOnly)) + return; + + int fd = f.handle(); + + //get the list of formats: + QList<quint32> supportedFormats; + + { + v4l2_fmtdesc fmt; + memset(&fmt, 0, sizeof(v4l2_fmtdesc)); + + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int sanity = 0; + + for (fmt.index = 0;; fmt.index++) { + if (sanity++ > 8) + break; + if( ::ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == -1) { + if(errno == EINVAL) + break; + } + supportedFormats.append(fmt.pixelformat); + } + } + + QList<QSize> commonSizes; + commonSizes << QSize(128, 96) + <<QSize(160,120) + <<QSize(176, 144) + <<QSize(320, 240) + <<QSize(352, 288) + <<QSize(640, 480) + <<QSize(1024, 768) + <<QSize(1280, 1024) + <<QSize(1600, 1200) + <<QSize(1920, 1200) + <<QSize(2048, 1536) + <<QSize(2560, 1600) + <<QSize(2580, 1936); + + QList<int> commonRates; + commonRates << 05*1000 << 75*1000 << 10*1000 << 15*1000 << 20*1000 + << 24*1000 << 25*1000 << 30*1000 << 50*1000 << 60*1000; + + + //get the list of resolutions: + + foreach (quint32 format, supportedFormats) { + struct v4l2_frmsizeenum formatSize; + memset(&formatSize, 0, sizeof(formatSize)); + formatSize.pixel_format = format; + + QList<QSize> sizeList; + + if (0) { + char formatStr[5]; + memcpy(formatStr, &format, 4); + formatStr[4] = 0; + //qDebug() << "trying format" << formatStr; + } + + for (int i=0;;i++) { + formatSize.index = i; + if (ioctl (fd, VIDIOC_ENUM_FRAMESIZES, &formatSize) < 0) + break; + + if (formatSize.type == V4L2_FRMSIZE_TYPE_DISCRETE) { + sizeList.append(QSize(formatSize.discrete.width, formatSize.discrete.height)); + } else { + + foreach (const QSize& candidate, commonSizes) { + if (candidate.width() <= (int)formatSize.stepwise.max_width && + candidate.height() >= (int)formatSize.stepwise.min_width && + candidate.width() % formatSize.stepwise.step_width == 0 && + candidate.height() <= (int)formatSize.stepwise.max_height && + candidate.height() >= (int)formatSize.stepwise.min_height && + candidate.height() % formatSize.stepwise.step_height == 0) { + sizeList.append(candidate); + } + } + + if (!sizeList.contains(QSize(formatSize.stepwise.min_width, formatSize.stepwise.min_height))) + sizeList.prepend(QSize(formatSize.stepwise.min_width, formatSize.stepwise.min_height)); + + if (!sizeList.contains(QSize(formatSize.stepwise.max_width, formatSize.stepwise.max_height))) + sizeList.append(QSize(formatSize.stepwise.max_width, formatSize.stepwise.max_height)); + + break; //stepwise values are returned only for index 0 + } + + } + + //and frameRates for each resolution. + + foreach (const QSize &s, sizeList) { + allResolutions.insert(s); + + struct v4l2_frmivalenum formatInterval; + memset(&formatInterval, 0, sizeof(formatInterval)); + formatInterval.pixel_format = format; + formatInterval.width = s.width(); + formatInterval.height = s.height(); + + QList<int> frameRates; //in 1/1000 of fps + + for (int i=0; ; i++) { + formatInterval.index = i; + + if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &formatInterval) < 0) + break; + + if (formatInterval.type == V4L2_FRMIVAL_TYPE_DISCRETE) { + //converts seconds to fps*1000 + if (formatInterval.discrete.numerator) + frameRates.append(qRound(formatInterval.discrete.denominator*1000.0 / formatInterval.discrete.numerator)); + } else { + if (formatInterval.stepwise.min.numerator == 0 || + formatInterval.stepwise.max.numerator == 0) { + qWarning() << "received invalid frame interval"; + break; + } + + + int minRate = qRound(formatInterval.stepwise.min.denominator*1000.0 / + formatInterval.stepwise.min.numerator); + + int maxRate = qRound(formatInterval.stepwise.max.denominator*1000.0 / + formatInterval.stepwise.max.numerator); + + + foreach (int candidate, commonRates) { + if (candidate >= minRate && candidate <= maxRate) + frameRates.append(candidate); + } + + if (!frameRates.contains(minRate)) + frameRates.prepend(minRate); + + if (!frameRates.contains(maxRate)) + frameRates.append(maxRate); + + break; //stepwise values are returned only for index 0 + } + } + allFrameRates.unite(frameRates.toSet()); + m_ratesByResolution[s].unite(frameRates.toSet()); + } + } + + f.close(); + + foreach(int rate, allFrameRates) { + m_frameRates.append(rate/1000.0); + } + + qSort(m_frameRates); + + m_resolutions = allResolutions.toList(); + qSort(m_resolutions); + + //qDebug() << "frame rates:" << m_frameRates; + //qDebug() << "resolutions:" << m_resolutions; +} + + +QList<qreal> QGstreamerV4L2Input::supportedFrameRates(const QSize &frameSize) const +{ + if (frameSize.isEmpty()) + return m_frameRates; + else { + QList<qreal> res; + foreach(int rate, m_ratesByResolution[frameSize]) { + res.append(rate/1000.0); + } + return res; + } +} + +QList<QSize> QGstreamerV4L2Input::supportedResolutions(qreal frameRate) const +{ + Q_UNUSED(frameRate); + return m_resolutions; +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.h b/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.h new file mode 100644 index 000000000..5dbdd5453 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamerv4l2input.h @@ -0,0 +1,83 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERV4L2INPUT_H +#define QGSTREAMERV4L2INPUT_H + +#include <QtCore/qhash.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qlist.h> +#include <QtCore/qsize.h> +#include "qgstreamercapturesession.h" + +QT_USE_NAMESPACE + +class QGstreamerV4L2Input : public QObject, public QGstreamerVideoInput +{ + Q_OBJECT +public: + QGstreamerV4L2Input(QObject *parent = 0); + virtual ~QGstreamerV4L2Input(); + + GstElement *buildElement(); + + QList<qreal> supportedFrameRates(const QSize &frameSize = QSize()) const; + QList<QSize> supportedResolutions(qreal frameRate = -1) const; + + QByteArray device() const; + +public slots: + void setDevice(const QByteArray &device); + void setDevice(const QString &device); + +private: + void updateSupportedResolutions(const QByteArray &device); + + QList<qreal> m_frameRates; + QList<QSize> m_resolutions; + + QHash<QSize, QSet<int> > m_ratesByResolution; + + QByteArray m_device; +}; + +#endif // QGSTREAMERV4L2INPUT_H diff --git a/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.cpp b/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.cpp new file mode 100644 index 000000000..a491f14ae --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideoencode.h" +#include "qgstreamercapturesession.h" +#include "qgstreamermediacontainercontrol.h" + +#include <QtCore/qdebug.h> + +#include <math.h> + +QGstreamerVideoEncode::QGstreamerVideoEncode(QGstreamerCaptureSession *session) + :QVideoEncoderControl(session), m_session(session) +{ + QList<QByteArray> codecCandidates; + codecCandidates << "video/h264" << "video/xvid" << "video/mpeg4" << "video/mpeg1" << "video/mpeg2" << "video/theora"; + + m_elementNames["video/h264"] = "x264enc"; + m_elementNames["video/xvid"] = "xvidenc"; + m_elementNames["video/mpeg4"] = "ffenc_mpeg4"; + m_elementNames["video/mpeg1"] = "ffenc_mpeg1video"; + m_elementNames["video/mpeg2"] = "ffenc_mpeg2video"; + m_elementNames["video/theora"] = "theoraenc"; + + m_codecOptions["video/h264"] = QStringList() << "quantizer"; + m_codecOptions["video/xvid"] = QStringList() << "quantizer" << "profile"; + m_codecOptions["video/mpeg4"] = QStringList() << "quantizer"; + m_codecOptions["video/mpeg1"] = QStringList() << "quantizer"; + m_codecOptions["video/mpeg2"] = QStringList() << "quantizer"; + m_codecOptions["video/theora"] = QStringList(); + + foreach( const QByteArray& codecName, codecCandidates ) { + QByteArray elementName = m_elementNames[codecName]; + GstElementFactory *factory = gst_element_factory_find(elementName.constData()); + if (factory) { + m_codecs.append(codecName); + const gchar *descr = gst_element_factory_get_description(factory); + m_codecDescriptions.insert(codecName, QString::fromUtf8(descr)); + + m_streamTypes.insert(codecName, + QGstreamerMediaContainerControl::supportedStreamTypes(factory, GST_PAD_SRC)); + + gst_object_unref(GST_OBJECT(factory)); + } + } + + //if (!m_codecs.isEmpty()) + // m_videoSettings.setCodec(m_codecs[0]); +} + +QGstreamerVideoEncode::~QGstreamerVideoEncode() +{ +} + +QList<QSize> QGstreamerVideoEncode::supportedResolutions(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = m_session->videoInput() != 0; + + return m_session->videoInput() ? m_session->videoInput()->supportedResolutions() : QList<QSize>(); +} + +QList< qreal > QGstreamerVideoEncode::supportedFrameRates(const QVideoEncoderSettings &, bool *continuous) const +{ + if (continuous) + *continuous = false; + + return m_session->videoInput() ? m_session->videoInput()->supportedFrameRates() : QList<qreal>(); +} + +QStringList QGstreamerVideoEncode::supportedVideoCodecs() const +{ + return m_codecs; +} + +QString QGstreamerVideoEncode::videoCodecDescription(const QString &codecName) const +{ + return m_codecDescriptions.value(codecName); +} + +QStringList QGstreamerVideoEncode::supportedEncodingOptions(const QString &codec) const +{ + return m_codecOptions.value(codec); +} + +QVariant QGstreamerVideoEncode::encodingOption(const QString &codec, const QString &name) const +{ + return m_options[codec].value(name); +} + +void QGstreamerVideoEncode::setEncodingOption( + const QString &codec, const QString &name, const QVariant &value) +{ + m_options[codec][name] = value; +} + +QVideoEncoderSettings QGstreamerVideoEncode::videoSettings() const +{ + return m_videoSettings; +} + +void QGstreamerVideoEncode::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoSettings = settings; +} + +GstElement *QGstreamerVideoEncode::createEncoder() +{ + QString codec = m_videoSettings.codec(); + //qDebug() << "create encoder for video codec" << codec; + GstElement *encoderElement = gst_element_factory_make( m_elementNames.value(codec).constData(), "video-encoder"); + if (!encoderElement) + return 0; + + GstBin *encoderBin = GST_BIN(gst_bin_new("video-encoder-bin")); + + GstElement *capsFilter = gst_element_factory_make("capsfilter", "capsfilter-video"); + gst_bin_add(encoderBin, capsFilter); + + GstElement *colorspace = gst_element_factory_make("ffmpegcolorspace", NULL); + gst_bin_add(encoderBin, colorspace); + gst_bin_add(encoderBin, encoderElement); + + gst_element_link_many(capsFilter, colorspace, encoderElement, NULL); + + // add ghostpads + GstPad *pad = gst_element_get_static_pad(capsFilter, "sink"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + pad = gst_element_get_static_pad(encoderElement, "src"); + gst_element_add_pad(GST_ELEMENT(encoderBin), gst_ghost_pad_new("src", pad)); + gst_object_unref(GST_OBJECT(pad)); + + if (encoderElement) { + if (m_videoSettings.encodingMode() == QtMultimediaKit::ConstantQualityEncoding) { + QtMultimediaKit::EncodingQuality qualityValue = m_videoSettings.quality(); + + if (codec == QLatin1String("video/h264")) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 4, NULL); + int qualityTable[] = { + 50, //VeryLow + 35, //Low + 21, //Normal + 15, //High + 8 //VeryHigh + }; + g_object_set(G_OBJECT(encoderElement), "quantizer", qualityTable[qualityValue], NULL); + } else if (codec == QLatin1String("video/xvid")) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 3, NULL); + int qualityTable[] = { + 32, //VeryLow + 12, //Low + 5, //Normal + 3, //High + 2 //VeryHigh + }; + int quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (codec == QLatin1String("video/mpeg4") || + codec == QLatin1String("video/mpeg1") || + codec == QLatin1String("video/mpeg2") ) { + //constant quantizer mode + g_object_set(G_OBJECT(encoderElement), "pass", 2, NULL); + //quant from 1 to 30, default ~3 + double qualityTable[] = { + 20, //VeryLow + 8.0, //Low + 3.0, //Normal + 2.5, //High + 2.0 //VeryHigh + }; + double quant = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quantizer", quant, NULL); + } else if (codec == QLatin1String("video/theora")) { + int qualityTable[] = { + 8, //VeryLow + 16, //Low + 32, //Normal + 45, //High + 60 //VeryHigh + }; + //quality from 0 to 63 + int quality = qualityTable[qualityValue]; + g_object_set(G_OBJECT(encoderElement), "quality", quality, NULL); + } + } else { + int bitrate = m_videoSettings.bitRate(); + if (bitrate > 0) { + g_object_set(G_OBJECT(encoderElement), "bitrate", bitrate, NULL); + } + } + + QMap<QString,QVariant> options = m_options.value(codec); + QMapIterator<QString,QVariant> it(options); + while (it.hasNext()) { + it.next(); + QString option = it.key(); + QVariant value = it.value(); + + switch (value.type()) { + case QVariant::Int: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toInt(), NULL); + break; + case QVariant::Bool: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toBool(), NULL); + break; + case QVariant::Double: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toDouble(), NULL); + break; + case QVariant::String: + g_object_set(G_OBJECT(encoderElement), option.toAscii(), value.toString().toUtf8().constData(), NULL); + break; + default: + qWarning() << "unsupported option type:" << option << value; + break; + } + + } + } + + 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"; + + foreach(const QString &structureType, structureTypes) { + GstStructure *structure = gst_structure_new(structureType.toAscii().constData(), 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); + } + + //qDebug() << "set video caps filter:" << gst_caps_to_string(caps); + + g_object_set(G_OBJECT(capsFilter), "caps", caps, NULL); + } + + return GST_ELEMENT(encoderBin); +} + +QPair<int,int> QGstreamerVideoEncode::rateAsRational() const +{ + qreal frameRate = m_videoSettings.frameRate(); + + if (frameRate > 0.001) { + //convert to rational number + QList<int> denumCandidates; + denumCandidates << 1 << 2 << 3 << 5 << 10 << 1001 << 1000; + + qreal error = 1.0; + int num = 1; + int denum = 1; + + foreach (int curDenum, denumCandidates) { + int curNum = qRound(frameRate*curDenum); + qreal curError = qAbs(qreal(curNum)/curDenum - frameRate); + + if (curError < error) { + error = curError; + num = curNum; + denum = curDenum; + } + + if (curError < 1e-8) + break; + } + + return QPair<int,int>(num,denum); + } + + return QPair<int,int>(); +} + + +QSet<QString> QGstreamerVideoEncode::supportedStreamTypes(const QString &codecName) const +{ + return m_streamTypes.value(codecName); +} diff --git a/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.h b/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.h new file mode 100644 index 000000000..c042efd25 --- /dev/null +++ b/src/plugins/gstreamer/mediacapture/qgstreamervideoencode.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOENCODE_H +#define QGSTREAMERVIDEOENCODE_H + +#include <qvideoencodercontrol.h> +class QGstreamerCaptureSession; + +#include <QtCore/qstringlist.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> + +#include <gst/gst.h> + +QT_USE_NAMESPACE + +class QGstreamerVideoEncode : public QVideoEncoderControl +{ + Q_OBJECT +public: + QGstreamerVideoEncode(QGstreamerCaptureSession *session); + virtual ~QGstreamerVideoEncode(); + + QList<QSize> supportedResolutions(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const; + + QList< qreal > supportedFrameRates(const QVideoEncoderSettings &settings = QVideoEncoderSettings(), + bool *continuous = 0) const; + + QPair<int,int> rateAsRational() const; + + QStringList supportedVideoCodecs() const; + QString videoCodecDescription(const QString &codecName) const; + + QVideoEncoderSettings videoSettings() const; + void setVideoSettings(const QVideoEncoderSettings &settings); + + QStringList supportedEncodingOptions(const QString &codec) const; + QVariant encodingOption(const QString &codec, const QString &name) const; + void setEncodingOption(const QString &codec, const QString &name, const QVariant &value); + + GstElement *createEncoder(); + + QSet<QString> supportedStreamTypes(const QString &codecName) const; + +private: + QGstreamerCaptureSession *m_session; + + QStringList m_codecs; + QMap<QString,QString> m_codecDescriptions; + QMap<QString,QByteArray> m_elementNames; + QMap<QString,QStringList> m_codecOptions; + + QVideoEncoderSettings m_videoSettings; + QMap<QString, QMap<QString, QVariant> > m_options; + QMap<QString, QSet<QString> > m_streamTypes; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/mediaplayer.pri b/src/plugins/gstreamer/mediaplayer/mediaplayer.pri new file mode 100644 index 000000000..9045a80dd --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/mediaplayer.pri @@ -0,0 +1,30 @@ +INCLUDEPATH += $$PWD + +DEFINES += QMEDIA_GSTREAMER_PLAYER + +contains(gstreamer-appsrc_enabled, yes) { + HEADERS += $$PWD/qgstappsrc.h + SOURCES += $$PWD/qgstappsrc.cpp + + DEFINES += HAVE_GST_APPSRC + + LIBS += -lgstapp-0.10 +} + +HEADERS += \ + $$PWD/qgstreamerplayercontrol.h \ + $$PWD/qgstreamerplayerservice.h \ + $$PWD/qgstreamerplayersession.h \ + $$PWD/qgstreamerstreamscontrol.h \ + $$PWD/qgstreamermetadataprovider.h \ + $$PWD/playerresourcepolicy.h + +SOURCES += \ + $$PWD/qgstreamerplayercontrol.cpp \ + $$PWD/qgstreamerplayerservice.cpp \ + $$PWD/qgstreamerplayersession.cpp \ + $$PWD/qgstreamerstreamscontrol.cpp \ + $$PWD/qgstreamermetadataprovider.cpp \ + $$PWD/playerresourcepolicy.cpp + + diff --git a/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp new file mode 100644 index 000000000..b2cf3498d --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.cpp @@ -0,0 +1,180 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "playerresourcepolicy.h" + +#ifdef Q_WS_MAEMO_6 +#define HAVE_RESOURCE_POLICY +#endif + +//#define DEBUG_RESOURCE_POLICY +#include <QtCore/qdebug.h> + +#ifdef HAVE_RESOURCE_POLICY +#include <policy/resource.h> +#include <policy/resources.h> +#include <policy/resource-set.h> +#endif + +PlayerResourcePolicy::PlayerResourcePolicy(QObject *parent) : + QObject(parent), + m_videoEnabled(true), + m_resourceSet(0), + m_status(PlayerResourcePolicy::Initial) +{ +#ifdef HAVE_RESOURCE_POLICY + m_resourceSet = new ResourcePolicy::ResourceSet("player", this); + m_resourceSet->setAlwaysReply(); + + ResourcePolicy::AudioResource *audioResource = new ResourcePolicy::AudioResource("player"); + audioResource->setProcessID(QCoreApplication::applicationPid()); + audioResource->setStreamTag("media.name", "*"); + m_resourceSet->addResourceObject(audioResource); + + m_resourceSet->addResource(ResourcePolicy::VideoPlaybackType); + m_resourceSet->update(); + + connect(m_resourceSet, SIGNAL(resourcesGranted(const QList<ResourcePolicy::ResourceType>)), + this, SLOT(handleResourcesGranted())); + connect(m_resourceSet, SIGNAL(resourcesDenied()), + this, SLOT(handleResourcesDenied())); + connect(m_resourceSet, SIGNAL(lostResources()), + this, SLOT(handleResourcesLost())); + connect(m_resourceSet, SIGNAL(resourcesReleasedByManager()), + this, SLOT(handleResourcesLost())); +#endif +} + +PlayerResourcePolicy::~PlayerResourcePolicy() +{ +} + +bool PlayerResourcePolicy::isVideoEnabled() const +{ + return m_videoEnabled; +} + +void PlayerResourcePolicy::setVideoEnabled(bool enabled) +{ + if (m_videoEnabled != enabled) { + m_videoEnabled = enabled; + +#ifdef HAVE_RESOURCE_POLICY + if (enabled) + m_resourceSet->addResource(ResourcePolicy::VideoPlaybackType); + else + m_resourceSet->deleteResource(ResourcePolicy::VideoPlaybackType); + + m_resourceSet->update(); +#endif + } +} + +void PlayerResourcePolicy::acquire() +{ +#ifdef HAVE_RESOURCE_POLICY + +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Acquire resource"; +#endif + m_status = RequestedResource; + m_resourceSet->acquire(); +#else + m_status = GrantedResource; +#endif +} + +void PlayerResourcePolicy::release() +{ +#ifdef HAVE_RESOURCE_POLICY + +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Release resource"; +#endif + + m_resourceSet->release(); +#endif + m_status = Initial; + +} + +bool PlayerResourcePolicy::isGranted() const +{ + return m_status == GrantedResource; +} + +bool PlayerResourcePolicy::isRequested() const +{ + return m_status == RequestedResource; +} + +void PlayerResourcePolicy::handleResourcesGranted() +{ + m_status = GrantedResource; +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource granted"; +#endif + emit resourcesGranted(); +} + +void PlayerResourcePolicy::handleResourcesDenied() +{ + m_status = Initial; +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource denied"; +#endif + emit resourcesDenied(); +} + +void PlayerResourcePolicy::handleResourcesLost() +{ +#ifdef DEBUG_RESOURCE_POLICY + qDebug() << Q_FUNC_INFO << "Resource lost"; +#endif + if (m_status != Initial) { + m_status = Initial; + emit resourcesLost(); + } + +#ifdef HAVE_RESOURCE_POLICY + m_resourceSet->release(); +#endif +} diff --git a/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h new file mode 100644 index 000000000..fecf5fa80 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/playerresourcepolicy.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PLAYERRESOURCEPOLICY_H +#define PLAYERRESOURCEPOLICY_H + +#include <QtCore/qobject.h> + +namespace ResourcePolicy { +class ResourceSet; +}; + +class PlayerResourcePolicy : public QObject +{ + Q_OBJECT +public: + PlayerResourcePolicy(QObject *parent = 0); + ~PlayerResourcePolicy(); + + bool isVideoEnabled() const; + bool isGranted() const; + bool isRequested() const; + +Q_SIGNALS: + void resourcesDenied(); + void resourcesGranted(); + void resourcesLost(); + +public Q_SLOTS: + void acquire(); + void release(); + + void setVideoEnabled(bool enabled); + +private Q_SLOTS: + void handleResourcesGranted(); + void handleResourcesDenied(); + void handleResourcesLost(); + +private: + enum ResourceStatus { + Initial = 0, + RequestedResource, + GrantedResource + }; + + bool m_videoEnabled; + ResourcePolicy::ResourceSet *m_resourceSet; + ResourceStatus m_status; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp b/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp new file mode 100644 index 000000000..cf814c462 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstappsrc.cpp @@ -0,0 +1,224 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> + +#include "qgstappsrc.h" +#include <QtNetwork> + +QGstAppSrc::QGstAppSrc(QObject *parent) + :QObject(parent) + ,m_stream(0) + ,m_appSrc(0) + ,m_sequential(false) + ,m_maxBytes(0) + ,m_setup(false) + ,m_dataRequestSize(-1) + ,m_dataRequested(false) + ,m_enoughData(false) + ,m_forceData(false) +{ +} + +QGstAppSrc::~QGstAppSrc() +{ + if (m_appSrc) + gst_object_unref(G_OBJECT(m_appSrc)); +} + +bool QGstAppSrc::setup(GstElement* appsrc) +{ + if (m_setup || m_stream == 0 || appsrc == 0) + return false; + + m_appSrc = GST_APP_SRC(appsrc); + m_callbacks.need_data = &QGstAppSrc::on_need_data; + m_callbacks.enough_data = &QGstAppSrc::on_enough_data; + m_callbacks.seek_data = &QGstAppSrc::on_seek_data; + gst_app_src_set_callbacks(m_appSrc, (GstAppSrcCallbacks*)&m_callbacks, this, (GDestroyNotify)&QGstAppSrc::destroy_notify); + + g_object_get(G_OBJECT(m_appSrc), "max-bytes", &m_maxBytes, NULL); + + if (m_sequential) + m_streamType = GST_APP_STREAM_TYPE_STREAM; + else + m_streamType = GST_APP_STREAM_TYPE_RANDOM_ACCESS; + gst_app_src_set_stream_type(m_appSrc, m_streamType); + gst_app_src_set_size(m_appSrc, (m_sequential) ? -1 : m_stream->size()); + + return m_setup = true; +} + +void QGstAppSrc::setStream(QIODevice *stream) +{ + if (stream == 0) + return; + if (m_stream) { + disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); + disconnect(m_stream, SIGNAL(destroyed()), this, SLOT(streamDestroyed())); + } + if (m_appSrc) + gst_object_unref(G_OBJECT(m_appSrc)); + + m_dataRequestSize = -1; + m_dataRequested = false; + m_enoughData = false; + m_forceData = false; + m_maxBytes = 0; + + m_appSrc = 0; + m_stream = stream; + connect(m_stream, SIGNAL(destroyed()), SLOT(streamDestroyed())); + connect(m_stream, SIGNAL(readyRead()), this, SLOT(onDataReady())); + m_sequential = m_stream->isSequential(); + m_setup = false; +} + +QIODevice *QGstAppSrc::stream() const +{ + return m_stream; +} + +GstAppSrc *QGstAppSrc::element() +{ + return m_appSrc; +} + +void QGstAppSrc::onDataReady() +{ + if (!m_enoughData) { + m_dataRequested = true; + pushDataToAppSrc(); + } +} + +void QGstAppSrc::streamDestroyed() +{ + if (sender() == m_stream) { + m_stream = 0; + sendEOS(); + } +} + +void QGstAppSrc::pushDataToAppSrc() +{ + if (!isStreamValid() || !m_setup) + return; + + if (m_dataRequested && !m_enoughData) { + qint64 size; + if (m_dataRequestSize == (unsigned int)-1) + size = qMin(m_stream->bytesAvailable(), queueSize()); + else + size = qMin(m_stream->bytesAvailable(), (qint64)m_dataRequestSize); + void *data = g_malloc(size); + GstBuffer* buffer = gst_app_buffer_new(data, size, g_free, data); + buffer->offset = m_stream->pos(); + qint64 bytesRead = m_stream->read((char*)GST_BUFFER_DATA(buffer), size); + buffer->offset_end = buffer->offset + bytesRead - 1; + + 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"; + } else if (ret == GST_FLOW_WRONG_STATE) { + qWarning()<<"appsrc: push buffer wrong state"; + } else if (ret == GST_FLOW_RESEND) { + qWarning()<<"appsrc: push buffer resend"; + } + } + } else if (m_stream->atEnd()) { + sendEOS(); + } +} + +bool QGstAppSrc::doSeek(qint64 value) +{ + if (isStreamValid()) + return stream()->seek(value); + return false; +} + + +gboolean QGstAppSrc::on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self && self->isStreamValid()) { + if (!self->stream()->isSequential()) + QMetaObject::invokeMethod(self, "doSeek", Qt::AutoConnection, Q_ARG(qint64, arg0)); + } + else + return false; + + return true; +} + +void QGstAppSrc::on_enough_data(GstAppSrc *element, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self) + self->enoughData() = true; +} + +void QGstAppSrc::on_need_data(GstAppSrc *element, guint arg0, gpointer userdata) +{ + QGstAppSrc *self = reinterpret_cast<QGstAppSrc*>(userdata); + if (self) { + self->dataRequested() = true; + self->enoughData() = false; + self->dataRequestSize()= arg0; + QMetaObject::invokeMethod(self, "pushDataToAppSrc", Qt::AutoConnection); + } +} + +void QGstAppSrc::destroy_notify(gpointer data) +{ + Q_UNUSED(data); +} + +void QGstAppSrc::sendEOS() +{ + gst_app_src_end_of_stream(GST_APP_SRC(m_appSrc)); + if (isStreamValid() && !stream()->isSequential()) + stream()->reset(); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstappsrc.h b/src/plugins/gstreamer/mediaplayer/qgstappsrc.h new file mode 100644 index 000000000..17ee2af72 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstappsrc.h @@ -0,0 +1,106 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTAPPSRC_H +#define QGSTAPPSRC_H + +#include <QtCore/qobject.h> +#include <QtCore/qiodevice.h> + +#include <gst/gst.h> +#include <gst/app/gstappsrc.h> +#include <gst/app/gstappbuffer.h> + +class QGstAppSrc : public QObject +{ + Q_OBJECT +public: + QGstAppSrc(QObject *parent = 0); + ~QGstAppSrc(); + + bool setup(GstElement *); + bool isReady() const { return m_setup; } + + void setStream(QIODevice *); + QIODevice *stream() const; + + GstAppSrc *element(); + + qint64 queueSize() const { return m_maxBytes; } + + bool& enoughData() { return m_enoughData; } + bool& dataRequested() { return m_dataRequested; } + unsigned int& dataRequestSize() { return m_dataRequestSize; } + + bool isStreamValid() const + { + return m_stream != 0 && + m_stream->isOpen(); + } + +private slots: + void pushDataToAppSrc(); + bool doSeek(qint64); + void onDataReady(); + + void streamDestroyed(); +private: + static gboolean on_seek_data(GstAppSrc *element, guint64 arg0, gpointer userdata); + static void on_enough_data(GstAppSrc *element, gpointer userdata); + static void on_need_data(GstAppSrc *element, uint arg0, gpointer userdata); + static void destroy_notify(gpointer data); + + void sendEOS(); + + QIODevice *m_stream; + GstAppSrc *m_appSrc; + bool m_sequential; + GstAppStreamType m_streamType; + GstAppSrcCallbacks m_callbacks; + qint64 m_maxBytes; + bool m_setup; + unsigned int m_dataRequestSize; + bool m_dataRequested; + bool m_enoughData; + bool m_forceData; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp new file mode 100644 index 000000000..cdd6d89b4 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.cpp @@ -0,0 +1,192 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamermetadataprovider.h" +#include "qgstreamerplayersession.h" +#include <QDebug> + +#include <gst/gstversion.h> + +struct QGstreamerMetaDataKeyLookup +{ + QtMultimediaKit::MetaData key; + const char *token; +}; + +static const QGstreamerMetaDataKeyLookup qt_gstreamerMetaDataKeys[] = +{ + { QtMultimediaKit::Title, GST_TAG_TITLE }, + //{ QtMultimediaKit::SubTitle, 0 }, + //{ QtMultimediaKit::Author, 0 }, + { QtMultimediaKit::Comment, GST_TAG_COMMENT }, + { QtMultimediaKit::Description, GST_TAG_DESCRIPTION }, + //{ QtMultimediaKit::Category, 0 }, + { QtMultimediaKit::Genre, GST_TAG_GENRE }, + { QtMultimediaKit::Year, "year" }, + //{ QtMultimediaKit::UserRating, 0 }, + + { QtMultimediaKit::Language, GST_TAG_LANGUAGE_CODE }, + + { QtMultimediaKit::Publisher, GST_TAG_ORGANIZATION }, + { QtMultimediaKit::Copyright, GST_TAG_COPYRIGHT }, + //{ QtMultimediaKit::ParentalRating, 0 }, + //{ QtMultimediaKit::RatingOrganisation, 0 }, + + // Media + //{ QtMultimediaKit::Size, 0 }, + //{ QtMultimediaKit::MediaType, 0 }, + { QtMultimediaKit::Duration, GST_TAG_DURATION }, + + // Audio + { QtMultimediaKit::AudioBitRate, GST_TAG_BITRATE }, + { QtMultimediaKit::AudioCodec, GST_TAG_AUDIO_CODEC }, + //{ QtMultimediaKit::ChannelCount, 0 }, + //{ QtMultimediaKit::SampleRate, 0 }, + + // Music + { QtMultimediaKit::AlbumTitle, GST_TAG_ALBUM }, + { QtMultimediaKit::AlbumArtist, GST_TAG_ARTIST}, + { QtMultimediaKit::ContributingArtist, GST_TAG_PERFORMER }, +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 19) + { QtMultimediaKit::Composer, GST_TAG_COMPOSER }, +#endif + //{ QtMultimediaKit::Conductor, 0 }, + //{ QtMultimediaKit::Lyrics, 0 }, + //{ QtMultimediaKit::Mood, 0 }, + { QtMultimediaKit::TrackNumber, GST_TAG_TRACK_NUMBER }, + + //{ QtMultimediaKit::CoverArtUrlSmall, 0 }, + //{ QtMultimediaKit::CoverArtUrlLarge, 0 }, + + // Image/Video + { QtMultimediaKit::Resolution, "resolution" }, + { QtMultimediaKit::PixelAspectRatio, "pixel-aspect-ratio" }, + + // Video + //{ QtMultimediaKit::VideoFrameRate, 0 }, + //{ QtMultimediaKit::VideoBitRate, 0 }, + { QtMultimediaKit::VideoCodec, GST_TAG_VIDEO_CODEC }, + + //{ QtMultimediaKit::PosterUrl, 0 }, + + // Movie + //{ QtMultimediaKit::ChapterNumber, 0 }, + //{ QtMultimediaKit::Director, 0 }, + { QtMultimediaKit::LeadPerformer, GST_TAG_PERFORMER }, + //{ QtMultimediaKit::Writer, 0 }, + + // Photos + //{ QtMultimediaKit::CameraManufacturer, 0 }, + //{ QtMultimediaKit::CameraModel, 0 }, + //{ QtMultimediaKit::Event, 0 }, + //{ QtMultimediaKit::Subject, 0 } +}; + +QGstreamerMetaDataProvider::QGstreamerMetaDataProvider(QGstreamerPlayerSession *session, QObject *parent) + :QMetaDataReaderControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(tagsChanged()), SLOT(updateTags())); +} + +QGstreamerMetaDataProvider::~QGstreamerMetaDataProvider() +{ +} + +bool QGstreamerMetaDataProvider::isMetaDataAvailable() const +{ + return !m_session->tags().isEmpty(); +} + +bool QGstreamerMetaDataProvider::isWritable() const +{ + return false; +} + +QVariant QGstreamerMetaDataProvider::metaData(QtMultimediaKit::MetaData key) const +{ + static const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + + for (int i = 0; i < count; ++i) { + if (qt_gstreamerMetaDataKeys[i].key == key) { + return m_session->tags().value(QByteArray(qt_gstreamerMetaDataKeys[i].token)); + } + } + return QVariant(); +} + +QList<QtMultimediaKit::MetaData> QGstreamerMetaDataProvider::availableMetaData() const +{ + static QMap<QByteArray, QtMultimediaKit::MetaData> keysMap; + if (keysMap.isEmpty()) { + const int count = sizeof(qt_gstreamerMetaDataKeys) / sizeof(QGstreamerMetaDataKeyLookup); + for (int i = 0; i < count; ++i) { + keysMap[QByteArray(qt_gstreamerMetaDataKeys[i].token)] = qt_gstreamerMetaDataKeys[i].key; + } + } + + QList<QtMultimediaKit::MetaData> res; + foreach (const QByteArray &key, m_session->tags().keys()) { + QtMultimediaKit::MetaData tag = keysMap.value(key, QtMultimediaKit::MetaData(-1)); + if (tag != -1) + res.append(tag); + } + + return res; +} + +QVariant QGstreamerMetaDataProvider::extendedMetaData(const QString &key) const +{ + return m_session->tags().value(key.toLatin1()); +} + +QStringList QGstreamerMetaDataProvider::availableExtendedMetaData() const +{ + QStringList res; + foreach (const QByteArray &key, m_session->tags().keys()) + res.append(QString(key)); + + return res; +} + +void QGstreamerMetaDataProvider::updateTags() +{ + emit metaDataChanged(); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h new file mode 100644 index 000000000..fa0c0243f --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamermetadataprovider.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERMETADATAPROVIDER_H +#define QGSTREAMERMETADATAPROVIDER_H + +#include <qmetadatareadercontrol.h> + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerMetaDataProvider : public QMetaDataReaderControl +{ + Q_OBJECT +public: + QGstreamerMetaDataProvider( QGstreamerPlayerSession *session, QObject *parent ); + virtual ~QGstreamerMetaDataProvider(); + + bool isMetaDataAvailable() const; + bool isWritable() const; + + QVariant metaData(QtMultimediaKit::MetaData key) const; + QList<QtMultimediaKit::MetaData> availableMetaData() const; + + QVariant extendedMetaData(const QString &key) const ; + QStringList availableExtendedMetaData() const; + +private slots: + void updateTags(); + +private: + QGstreamerPlayerSession *m_session; +}; + +#endif // QGSTREAMERMETADATAPROVIDER_H diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp new file mode 100644 index 000000000..f36dd08a3 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.cpp @@ -0,0 +1,748 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerplayercontrol.h" +#include "qgstreamerplayersession.h" +#include "playerresourcepolicy.h" + +#include <qmediaplaylistnavigator.h> + + +#include <QtCore/qdir.h> +#include <QtCore/qsocketnotifier.h> +#include <QtCore/qurl.h> +#include <QtCore/qdebug.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +//#define DEBUG_PLAYBIN + +QGstreamerPlayerControl::QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent) + : QMediaPlayerControl(parent) + , m_ownStream(false) + , m_session(session) + , m_state(QMediaPlayer::StoppedState) + , m_mediaStatus(QMediaPlayer::NoMedia) + , m_bufferProgress(-1) + , m_seekToStartPending(false) + , m_pendingSeekPosition(-1) + , m_stream(0) + , m_fifoNotifier(0) + , m_fifoCanWrite(false) + , m_bufferSize(0) + , m_bufferOffset(0) +{ + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + m_resources = new PlayerResourcePolicy(this); + + connect(m_session, SIGNAL(positionChanged(qint64)), + this, SIGNAL(positionChanged(qint64))); + connect(m_session, SIGNAL(durationChanged(qint64)), + this, SIGNAL(durationChanged(qint64))); + connect(m_session, SIGNAL(mutedStateChanged(bool)), + this, SIGNAL(mutedChanged(bool))); + connect(m_session, SIGNAL(volumeChanged(int)), + this, SIGNAL(volumeChanged(int))); + connect(m_session, SIGNAL(stateChanged(QMediaPlayer::State)), + this, SLOT(updateSessionState(QMediaPlayer::State))); + connect(m_session,SIGNAL(bufferingProgressChanged(int)), + this, SLOT(setBufferProgress(int))); + connect(m_session, SIGNAL(playbackFinished()), + this, SLOT(processEOS())); + connect(m_session, SIGNAL(audioAvailableChanged(bool)), + this, SIGNAL(audioAvailableChanged(bool))); + connect(m_session, SIGNAL(videoAvailableChanged(bool)), + this, SIGNAL(videoAvailableChanged(bool))); + connect(m_session, SIGNAL(seekableChanged(bool)), + this, SIGNAL(seekableChanged(bool))); + connect(m_session, SIGNAL(error(int,QString)), + this, SIGNAL(error(int,QString))); + connect(m_session, SIGNAL(invalidMedia()), + this, SLOT(handleInvalidMedia())); + connect(m_session, SIGNAL(playbackRateChanged(qreal)), + this, SIGNAL(playbackRateChanged(qreal))); + connect(m_session, SIGNAL(seekableChanged(bool)), + this, SLOT(applyPendingSeek(bool))); + + connect(m_resources, SIGNAL(resourcesGranted()), SLOT(handleResourcesGranted())); + connect(m_resources, SIGNAL(resourcesDenied()), SLOT(handleResourcesLost())); + connect(m_resources, SIGNAL(resourcesLost()), SLOT(handleResourcesLost())); +} + +QGstreamerPlayerControl::~QGstreamerPlayerControl() +{ + if (m_fifoFd[0] >= 0) { + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + } +} + +qint64 QGstreamerPlayerControl::position() const +{ + return m_seekToStartPending ? 0 : m_session->position(); +} + +qint64 QGstreamerPlayerControl::duration() const +{ + return m_session->duration(); +} + +QMediaPlayer::State QGstreamerPlayerControl::state() const +{ + return m_state; +} + +QMediaPlayer::MediaStatus QGstreamerPlayerControl::mediaStatus() const +{ + return m_mediaStatus; +} + +int QGstreamerPlayerControl::bufferStatus() const +{ + if (m_bufferProgress == -1) { + return m_session->state() == QMediaPlayer::StoppedState ? 0 : 100; + } else + return m_bufferProgress; +} + +int QGstreamerPlayerControl::volume() const +{ + return m_session->volume(); +} + +bool QGstreamerPlayerControl::isMuted() const +{ + return m_session->isMuted(); +} + +bool QGstreamerPlayerControl::isSeekable() const +{ + return m_session->isSeekable(); +} + +QMediaTimeRange QGstreamerPlayerControl::availablePlaybackRanges() const +{ + return m_session->availablePlaybackRanges(); +} + +qreal QGstreamerPlayerControl::playbackRate() const +{ + return m_session->playbackRate(); +} + +void QGstreamerPlayerControl::setPlaybackRate(qreal rate) +{ + m_session->setPlaybackRate(rate); +} + +void QGstreamerPlayerControl::setPosition(qint64 pos) +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << pos/1000.0; +#endif + + pushState(); + + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + m_mediaStatus = QMediaPlayer::LoadedMedia; + m_seekToStartPending = true; + } + + if (m_session->isSeekable() && m_session->seek(pos)) { + m_seekToStartPending = false; + m_pendingSeekPosition = -1; + } else { + m_pendingSeekPosition = pos; + } + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::play() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + playOrPause(QMediaPlayer::PlayingState); +} + +void QGstreamerPlayerControl::pause() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + playOrPause(QMediaPlayer::PausedState); +} + +void QGstreamerPlayerControl::playOrPause(QMediaPlayer::State newState) +{ + if (m_mediaStatus == QMediaPlayer::NoMedia) + return; + + pushState(); +#ifdef Q_WS_MAEMO_6 + //this is a work around for the gstreamer bug, + //should be remove once it get fixed + if (newState == QMediaPlayer::PlayingState && m_mediaStatus == QMediaPlayer::InvalidMedia) { + setMedia(m_currentResource, m_stream); + } +#endif + + if (m_mediaStatus == QMediaPlayer::EndOfMedia) { + m_mediaStatus = QMediaPlayer::BufferedMedia; + m_seekToStartPending = true; + } + + if (!m_resources->isGranted() && !m_resources->isRequested()) + m_resources->acquire(); + + if (m_resources->isGranted()) { + if (m_seekToStartPending) { + m_session->pause(); + if (!m_session->seek(0)) { + m_bufferProgress = -1; + m_session->stop(); + m_mediaStatus = QMediaPlayer::LoadingMedia; + } + m_seekToStartPending = false; + } + + bool ok = false; + + if (newState == QMediaPlayer::PlayingState) + ok = m_session->play(); + else + ok = m_session->pause(); + + if (!ok) + newState = QMediaPlayer::StoppedState; + } + + if (m_mediaStatus == QMediaPlayer::InvalidMedia) + m_mediaStatus = QMediaPlayer::LoadingMedia; + + m_state = newState; + + if (m_mediaStatus == QMediaPlayer::EndOfMedia || m_mediaStatus == QMediaPlayer::LoadedMedia) { + if (m_bufferProgress == -1 || m_bufferProgress == 100) + m_mediaStatus = QMediaPlayer::BufferedMedia; + else + m_mediaStatus = QMediaPlayer::BufferingMedia; + } + + popAndNotifyState(); + + emit positionChanged(position()); +} + +void QGstreamerPlayerControl::stop() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + pushState(); + + if (m_state != QMediaPlayer::StoppedState) { + m_state = QMediaPlayer::StoppedState; + if (m_resources->isGranted()) + m_session->pause(); + + if (m_mediaStatus != QMediaPlayer::EndOfMedia) { + m_seekToStartPending = true; + emit positionChanged(position()); + } + } + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setVolume(int volume) +{ + m_session->setVolume(volume); +} + +void QGstreamerPlayerControl::setMuted(bool muted) +{ + m_session->setMuted(muted); +} + +QMediaContent QGstreamerPlayerControl::media() const +{ + return m_currentResource; +} + +const QIODevice *QGstreamerPlayerControl::mediaStream() const +{ + return m_stream; +} + +void QGstreamerPlayerControl::setMedia(const QMediaContent &content, QIODevice *stream) +{ +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO; +#endif + + pushState(); + + m_state = QMediaPlayer::StoppedState; + QMediaContent oldMedia = m_currentResource; + m_pendingSeekPosition = -1; + + if (!content.isNull() || stream) { + if (!m_resources->isRequested() && !m_resources->isGranted()) + m_resources->acquire(); + + if (!m_resources->isGranted()) { + m_currentResource = content; + m_stream = stream; + + m_state = QMediaPlayer::StoppedState; + m_mediaStatus = QMediaPlayer::LoadingMedia; + if (m_currentResource != oldMedia) + emit mediaChanged(m_currentResource); + popAndNotifyState(); + return; + } + } else { + m_resources->release(); + } + + m_session->stop(); + + bool userStreamValid = false; + + if (m_bufferProgress != -1) { + m_bufferProgress = -1; + emit bufferStatusChanged(0); + } + + if (m_stream) { +#if !defined(HAVE_GST_APPSRC) + closeFifo(); + + disconnect(m_stream, SIGNAL(readyRead()), this, SLOT(writeFifo())); +#endif + + if (m_ownStream) + delete m_stream; + m_stream = 0; + m_ownStream = false; + } + + // If the canonical URL refers to a Qt resource, open with QFile and use + // the stream playback capability to play. + if (stream == 0 && content.canonicalUrl().scheme() == QLatin1String("qrc")) { + stream = new QFile(QLatin1Char(':') + content.canonicalUrl().path(), this); + if (!stream->open(QIODevice::ReadOnly)) { + delete stream; + m_mediaStatus = QMediaPlayer::InvalidMedia; + m_currentResource = content; + emit mediaChanged(m_currentResource); + emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid Qt resource")); + if (m_state != QMediaPlayer::PlayingState) + m_resources->release(); + popAndNotifyState(); + return; + } + m_ownStream = true; + } + + m_currentResource = content; + m_stream = stream; + m_seekToStartPending = false; + + QNetworkRequest request; + + if (m_stream) { +#if !defined(HAVE_GST_APPSRC) + if (m_stream->isReadable() && openFifo()) { + request = QNetworkRequest(QUrl(QString(QLatin1String("fd://%1")).arg(m_fifoFd[0]))); + } +#else + userStreamValid = stream->isOpen() && m_stream->isReadable(); + request = content.canonicalRequest(); +#endif + } else if (!content.isNull()) { + request = content.canonicalRequest(); + } + +#if !defined(HAVE_GST_APPSRC) + m_session->loadFromUri(request); +#else + if (m_stream) { + if (userStreamValid){ + m_session->loadFromStream(request, m_stream); + } else { + m_mediaStatus = QMediaPlayer::InvalidMedia; + emit error(QMediaPlayer::FormatError, tr("Attempting to play invalid user stream")); + if (m_state != QMediaPlayer::PlayingState) + m_resources->release(); + popAndNotifyState(); + return; + } + } else + m_session->loadFromUri(request); +#endif + +#if !defined(HAVE_GST_APPSRC) + if (m_fifoFd[1] >= 0) { + m_fifoCanWrite = true; + + writeFifo(); + } +#endif + +#if defined(HAVE_GST_APPSRC) + if (!request.url().isEmpty() || userStreamValid) { +#else + if (!request.url().isEmpty()) { +#endif + m_mediaStatus = QMediaPlayer::LoadingMedia; + m_session->pause(); + } else { + m_mediaStatus = QMediaPlayer::NoMedia; + setBufferProgress(0); + } + + if (m_currentResource != oldMedia) + emit mediaChanged(m_currentResource); + + emit positionChanged(position()); + + if (content.isNull() && !stream) + m_resources->release(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setVideoOutput(QObject *output) +{ + m_session->setVideoRenderer(output); +} + +bool QGstreamerPlayerControl::isAudioAvailable() const +{ + return m_session->isAudioAvailable(); +} + +bool QGstreamerPlayerControl::isVideoAvailable() const +{ + return m_session->isVideoAvailable(); +} + +void QGstreamerPlayerControl::updateSessionState(QMediaPlayer::State state) +{ + pushState(); + + if (state == QMediaPlayer::StoppedState) + m_state = QMediaPlayer::StoppedState; + + updateMediaStatus(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::updateMediaStatus() +{ + pushState(); + QMediaPlayer::MediaStatus oldStatus = m_mediaStatus; + + switch (m_session->state()) { + case QMediaPlayer::StoppedState: + if (m_currentResource.isNull()) + m_mediaStatus = QMediaPlayer::NoMedia; + else if (oldStatus != QMediaPlayer::InvalidMedia) + m_mediaStatus = QMediaPlayer::LoadingMedia; + break; + + case QMediaPlayer::PlayingState: + case QMediaPlayer::PausedState: + if (m_state == QMediaPlayer::StoppedState) { + m_mediaStatus = QMediaPlayer::LoadedMedia; + } else { + if (m_bufferProgress == -1 || m_bufferProgress == 100) + m_mediaStatus = QMediaPlayer::BufferedMedia; + else + m_mediaStatus = QMediaPlayer::StalledMedia; + } + break; + } + + if (m_state == QMediaPlayer::PlayingState && !m_resources->isGranted()) + m_mediaStatus = QMediaPlayer::StalledMedia; + + //EndOfMedia status should be kept, until reset by pause, play or setMedia + if (oldStatus == QMediaPlayer::EndOfMedia) + m_mediaStatus = QMediaPlayer::EndOfMedia; + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::processEOS() +{ + pushState(); + m_mediaStatus = QMediaPlayer::EndOfMedia; + emit positionChanged(position()); + stop(); + popAndNotifyState(); +} + +void QGstreamerPlayerControl::setBufferProgress(int progress) +{ + if (m_bufferProgress == progress || m_mediaStatus == QMediaPlayer::NoMedia) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << progress; +#endif + m_bufferProgress = progress; + + if (m_resources->isGranted()) { + if (m_state == QMediaPlayer::PlayingState && + m_bufferProgress == 100 && + m_session->state() != QMediaPlayer::PlayingState) + m_session->play(); + + if (m_bufferProgress < 100 && + (m_session->state() == QMediaPlayer::PlayingState || + m_session->pendingState() == QMediaPlayer::PlayingState)) + m_session->pause(); + } + + updateMediaStatus(); + + emit bufferStatusChanged(m_bufferProgress); +} + +void QGstreamerPlayerControl::writeFifo() +{ + if (m_fifoCanWrite) { + qint64 bytesToRead = qMin<qint64>( + m_stream->bytesAvailable(), PIPE_BUF - m_bufferSize); + + if (bytesToRead > 0) { + int bytesRead = m_stream->read(&m_buffer[m_bufferOffset + m_bufferSize], bytesToRead); + + if (bytesRead > 0) + m_bufferSize += bytesRead; + } + + if (m_bufferSize > 0) { + int bytesWritten = ::write(m_fifoFd[1], &m_buffer[m_bufferOffset], size_t(m_bufferSize)); + + if (bytesWritten > 0) { + m_bufferOffset += bytesWritten; + m_bufferSize -= bytesWritten; + + if (m_bufferSize == 0) + m_bufferOffset = 0; + } else if (errno == EAGAIN) { + m_fifoCanWrite = false; + } else { + closeFifo(); + } + } + } + + m_fifoNotifier->setEnabled(m_stream->bytesAvailable() > 0); +} + +void QGstreamerPlayerControl::fifoReadyWrite(int socket) +{ + if (socket == m_fifoFd[1]) { + m_fifoCanWrite = true; + + writeFifo(); + } +} + +bool QGstreamerPlayerControl::openFifo() +{ + Q_ASSERT(m_fifoFd[0] < 0); + Q_ASSERT(m_fifoFd[1] < 0); + + if (::pipe(m_fifoFd) == 0) { + int flags = ::fcntl(m_fifoFd[1], F_GETFD); + + if (::fcntl(m_fifoFd[1], F_SETFD, flags | O_NONBLOCK) >= 0) { + m_fifoNotifier = new QSocketNotifier(m_fifoFd[1], QSocketNotifier::Write); + + connect(m_fifoNotifier, SIGNAL(activated(int)), this, SLOT(fifoReadyWrite(int))); + + return true; + } else { + qWarning("Failed to make pipe non blocking %d", errno); + + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + return false; + } + } else { + qWarning("Failed to create pipe %d", errno); + + return false; + } +} + +void QGstreamerPlayerControl::closeFifo() +{ + if (m_fifoFd[0] >= 0) { + delete m_fifoNotifier; + m_fifoNotifier = 0; + + ::close(m_fifoFd[0]); + ::close(m_fifoFd[1]); + m_fifoFd[0] = -1; + m_fifoFd[1] = -1; + + m_fifoCanWrite = false; + + m_bufferSize = 0; + m_bufferOffset = 0; + } +} + +void QGstreamerPlayerControl::applyPendingSeek(bool isSeekable) +{ + if (isSeekable && m_pendingSeekPosition != -1) + setPosition(m_pendingSeekPosition); +} + +void QGstreamerPlayerControl::handleInvalidMedia() +{ + pushState(); + m_mediaStatus = QMediaPlayer::InvalidMedia; + m_state = QMediaPlayer::StoppedState; + popAndNotifyState(); +} + +void QGstreamerPlayerControl::handleResourcesGranted() +{ + pushState(); + + QMediaPlayer::State state = m_state; + + //preserve m_pendingSeekPosition, it's reset on setMedia + qint64 pos = m_pendingSeekPosition; + setMedia(m_currentResource, m_stream); + + if (pos != -1) + setPosition(pos); + + if (state != QMediaPlayer::StoppedState) + playOrPause(state); + else + updateMediaStatus(); + + popAndNotifyState(); +} + +void QGstreamerPlayerControl::handleResourcesLost() +{ + //on resource lost the pipeline should be stopped + //player status is changed to paused + + pushState(); + QMediaPlayer::State oldState = m_state; + + qint64 pos = m_session->position(); + m_session->stop(); + m_pendingSeekPosition = pos; + + if (oldState != QMediaPlayer::StoppedState ) + m_state = QMediaPlayer::PausedState; + + popAndNotifyState(); +} + +bool QGstreamerPlayerControl::isMediaDownloadEnabled() const +{ + return m_session->property("mediaDownloadEnabled").toBool(); +} + +void QGstreamerPlayerControl::setMediaDownloadEnabled(bool enabled) +{ + m_session->setProperty("mediaDownloadEnabled", enabled); +} + +void QGstreamerPlayerControl::pushState() +{ + m_stateStack.push(m_state); + m_mediaStatusStack.push(m_mediaStatus); +} + +void QGstreamerPlayerControl::popAndNotifyState() +{ + Q_ASSERT(!m_stateStack.isEmpty()); + + QMediaPlayer::State oldState = m_stateStack.pop(); + QMediaPlayer::MediaStatus oldMediaStatus = m_mediaStatusStack.pop(); + + if (m_stateStack.isEmpty()) { + if (m_state != oldState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "State changed:" << m_state; +#endif + emit stateChanged(m_state); + } + + if (m_mediaStatus != oldMediaStatus) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Media status changed:" << m_mediaStatus; +#endif + emit mediaStatusChanged(m_mediaStatus); + } + } +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h new file mode 100644 index 000000000..5a53a0713 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayercontrol.h @@ -0,0 +1,157 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERCONTROL_H +#define QGSTREAMERPLAYERCONTROL_H + +#include <QtCore/qobject.h> +#include <QtCore/qstack.h> + +#include <qmediaplayercontrol.h> +#include <qmediaplayer.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE +class QMediaPlaylist; +class QMediaPlaylistNavigator; +class QSocketNotifier; +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; +class QGstreamerPlayerService; +class PlayerResourcePolicy; + +class QGstreamerPlayerControl : public QMediaPlayerControl +{ + Q_OBJECT + Q_PROPERTY(bool mediaDownloadEnabled READ isMediaDownloadEnabled WRITE setMediaDownloadEnabled) + +public: + QGstreamerPlayerControl(QGstreamerPlayerSession *session, QObject *parent = 0); + ~QGstreamerPlayerControl(); + + QMediaPlayer::State state() const; + QMediaPlayer::MediaStatus mediaStatus() const; + + qint64 position() const; + qint64 duration() const; + + int bufferStatus() const; + + int volume() const; + bool isMuted() const; + + bool isAudioAvailable() const; + bool isVideoAvailable() const; + void setVideoOutput(QObject *output); + + bool isSeekable() const; + QMediaTimeRange availablePlaybackRanges() const; + + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + + QMediaContent media() const; + const QIODevice *mediaStream() const; + void setMedia(const QMediaContent&, QIODevice *); + + bool isMediaDownloadEnabled() const; + void setMediaDownloadEnabled(bool enabled); + +public Q_SLOTS: + void setPosition(qint64 pos); + + void play(); + void pause(); + void stop(); + + void setVolume(int volume); + void setMuted(bool muted); + +private Q_SLOTS: + void writeFifo(); + void fifoReadyWrite(int socket); + + void updateSessionState(QMediaPlayer::State state); + void updateMediaStatus(); + void processEOS(); + void setBufferProgress(int progress); + void applyPendingSeek(bool isSeekable); + + void handleInvalidMedia(); + + void handleResourcesGranted(); + void handleResourcesLost(); + +private: + bool openFifo(); + void closeFifo(); + void playOrPause(QMediaPlayer::State state); + + void pushState(); + void popAndNotifyState(); + + bool m_ownStream; + QGstreamerPlayerSession *m_session; + QMediaPlayer::State m_state; + QMediaPlayer::MediaStatus m_mediaStatus; + QStack<QMediaPlayer::State> m_stateStack; + QStack<QMediaPlayer::MediaStatus> m_mediaStatusStack; + + int m_bufferProgress; + bool m_seekToStartPending; + qint64 m_pendingSeekPosition; + QMediaContent m_currentResource; + QIODevice *m_stream; + QSocketNotifier *m_fifoNotifier; + int m_fifoFd[2]; + bool m_fifoCanWrite; + int m_bufferSize; + int m_bufferOffset; + char m_buffer[PIPE_BUF]; + + PlayerResourcePolicy *m_resources; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp new file mode 100644 index 000000000..6976c18ae --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.cpp @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> +#include <QtGui/qwidget.h> + +#include "qgstreamerplayerservice.h" +#include "qgstreamerplayercontrol.h" +#include "qgstreamerplayersession.h" +#include "qgstreamermetadataprovider.h" + +#include "qgstreamervideooverlay.h" +#include "qgstreamervideowindow.h" +#include "qgstreamervideorenderer.h" + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) +#include "qgstreamergltexturerenderer.h" +#endif + +#include "qgstreamervideowidget.h" +#include "qgstreamerstreamscontrol.h" + +#include <qmediaplaylistnavigator.h> +#include <qmediaplaylist.h> + +QGstreamerPlayerService::QGstreamerPlayerService(QObject *parent): + QMediaService(parent), + m_videoOutput(0), + m_videoRenderer(0), + m_videoWindow(0), + m_videoWidget(0) +{ + m_session = new QGstreamerPlayerSession(this); + m_control = new QGstreamerPlayerControl(m_session, this); + m_metaData = new QGstreamerMetaDataProvider(m_session, this); + m_streamsControl = new QGstreamerStreamsControl(m_session,this); + +#if defined(Q_WS_MAEMO_6) && defined(__arm__) + m_videoRenderer = new QGstreamerGLTextureRenderer(this); +#else + m_videoRenderer = new QGstreamerVideoRenderer(this); +#endif + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + +#ifdef Q_WS_MAEMO_6 + m_videoWindow = new QGstreamerVideoWindow(this, "omapxvsink"); +#else + m_videoWindow = new QGstreamerVideoOverlay(this); +#endif + + m_videoWidget = new QGstreamerVideoWidgetControl(this); +#endif +} + +QGstreamerPlayerService::~QGstreamerPlayerService() +{ +} + +QMediaControl *QGstreamerPlayerService::requestControl(const char *name) +{ + if (qstrcmp(name,QMediaPlayerControl_iid) == 0) + return m_control; + + if (qstrcmp(name,QMetaDataReaderControl_iid) == 0) + return m_metaData; + + if (qstrcmp(name,QMediaStreamsControl_iid) == 0) + return m_streamsControl; + + if (!m_videoOutput) { + if (qstrcmp(name, QVideoWidgetControl_iid) == 0) + m_videoOutput = m_videoWidget; + else if (qstrcmp(name, QVideoRendererControl_iid) == 0) + m_videoOutput = m_videoRenderer; + else if (qstrcmp(name, QVideoWindowControl_iid) == 0) + m_videoOutput = m_videoWindow; + + if (m_videoOutput) { + m_control->setVideoOutput(m_videoOutput); + return m_videoOutput; + } + } + + return 0; +} + +void QGstreamerPlayerService::releaseControl(QMediaControl *control) +{ + if (control == m_videoOutput) { + m_videoOutput = 0; + m_control->setVideoOutput(0); + } +} + diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h new file mode 100644 index 000000000..92ea9ffa4 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayerservice.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERSERVICE_H +#define QGSTREAMERPLAYERSERVICE_H + +#include <QtCore/qobject.h> +#include <QtCore/qiodevice.h> + +#include <qmediaservice.h> + +QT_BEGIN_NAMESPACE +class QMediaMetaData; +class QMediaPlayerControl; +class QMediaPlaylist; +class QMediaPlaylistNavigator; +QT_END_NAMESPACE + +class QGstreamerMetaData; +class QGstreamerPlayerControl; +class QGstreamerPlayerSession; +class QGstreamerMetaDataProvider; +class QGstreamerStreamsControl; +class QGstreamerVideoRenderer; +class QGstreamerVideoOverlay; +class QGstreamerVideoWidgetControl; + +QT_USE_NAMESPACE + +class QGstreamerPlayerService : public QMediaService +{ + Q_OBJECT +public: + QGstreamerPlayerService(QObject *parent = 0); + ~QGstreamerPlayerService(); + + QMediaControl *requestControl(const char *name); + void releaseControl(QMediaControl *control); + +private: + QGstreamerPlayerControl *m_control; + QGstreamerPlayerSession *m_session; + QGstreamerMetaDataProvider *m_metaData; + QGstreamerStreamsControl *m_streamsControl; + + QMediaControl *m_videoOutput; + QMediaControl *m_videoRenderer; + QMediaControl *m_videoWindow; + QMediaControl *m_videoWidget; +}; + +#endif diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp new file mode 100644 index 000000000..d12bb2697 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.cpp @@ -0,0 +1,1537 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerplayersession.h" +#include "qgstreamerbushelper.h" + +#include "qgstreamervideorendererinterface.h" +#include "gstvideoconnector.h" +#include "qgstutils.h" + +#include <gst/gstvalue.h> + +#include <QtCore/qdatetime.h> +#include <QtCore/qdebug.h> +#include <QtCore/qsize.h> +#include <QtCore/qtimer.h> +#include <QtCore/qdebug.h> +#include <QtCore/qdir.h> +#include <QtGui/qdesktopservices.h> + +#if defined(Q_WS_MAEMO_5) || defined(Q_WS_MAEMO_6) || (GST_VERSION_MICRO > 20) +#define USE_PLAYBIN2 +#endif + +//#define DEBUG_PLAYBIN +//#define DEBUG_VO_BIN_DUMP + +typedef enum { + GST_PLAY_FLAG_VIDEO = 0x00000001, + GST_PLAY_FLAG_AUDIO = 0x00000002, + GST_PLAY_FLAG_TEXT = 0x00000004, + GST_PLAY_FLAG_VIS = 0x00000008, + GST_PLAY_FLAG_SOFT_VOLUME = 0x00000010, + GST_PLAY_FLAG_NATIVE_AUDIO = 0x00000020, + GST_PLAY_FLAG_NATIVE_VIDEO = 0x00000040, + GST_PLAY_FLAG_DOWNLOAD = 0x00000080, + GST_PLAY_FLAG_BUFFERING = 0x000000100 +} GstPlayFlags; + +QGstreamerPlayerSession::QGstreamerPlayerSession(QObject *parent) + :QObject(parent), + m_state(QMediaPlayer::StoppedState), + m_pendingState(QMediaPlayer::StoppedState), + m_busHelper(0), + m_playbin(0), + m_usePlaybin2(false), + m_usingColorspaceElement(false), + m_videoSink(0), + m_pendingVideoSink(0), + m_nullVideoSink(0), + m_bus(0), + m_videoOutput(0), + m_renderer(0), + m_haveQueueElement(false), +#if defined(HAVE_GST_APPSRC) + m_appSrc(0), +#endif + m_volume(100), + m_playbackRate(1.0), + m_muted(false), + m_audioAvailable(false), + m_videoAvailable(false), + m_seekable(false), + m_lastPosition(0), + m_duration(-1), + m_durationQueries(0), + m_everPlayed(false) , + m_sourceType(UnknownSrc) +{ +#ifdef USE_PLAYBIN2 + m_playbin = gst_element_factory_make("playbin2", NULL); +#endif + + if (m_playbin) { + m_usePlaybin2 = true; + + //GST_PLAY_FLAG_NATIVE_VIDEO omits configuration of ffmpegcolorspace and videoscale, + //since those elements are included in the video output bin when necessary. +#ifdef Q_WS_MAEMO_6 + 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); + flags |= GST_PLAY_FLAG_NATIVE_VIDEO; +#endif + g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL); + } else { + m_usePlaybin2 = false; + m_playbin = gst_element_factory_make("playbin", NULL); + } + + m_videoOutputBin = gst_bin_new("video-output-bin"); + gst_object_ref(GST_OBJECT(m_videoOutputBin)); + + m_videoIdentity = GST_ELEMENT(g_object_new(gst_video_connector_get_type(), 0)); + g_signal_connect(G_OBJECT(m_videoIdentity), "connection-failed", G_CALLBACK(insertColorSpaceElement), (gpointer)this); + m_colorSpace = gst_element_factory_make("ffmpegcolorspace", "ffmpegcolorspace-vo"); + gst_object_ref(GST_OBJECT(m_colorSpace)); + + m_nullVideoSink = gst_element_factory_make("fakesink", NULL); + g_object_set(G_OBJECT(m_nullVideoSink), "sync", true, NULL); + gst_object_ref(GST_OBJECT(m_nullVideoSink)); + gst_bin_add_many(GST_BIN(m_videoOutputBin), m_videoIdentity, m_nullVideoSink, NULL); + gst_element_link(m_videoIdentity, m_nullVideoSink); + + m_videoSink = m_nullVideoSink; + + // 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_object_unref(GST_OBJECT(pad)); + + if (m_playbin != 0) { + // Sort out messages + m_bus = gst_element_get_bus(m_playbin); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + connect(m_busHelper, SIGNAL(message(QGstreamerMessage)), SLOT(busMessage(QGstreamerMessage))); + m_busHelper->installSyncEventFilter(this); + + g_object_set(G_OBJECT(m_playbin), "video-sink", m_videoOutputBin, NULL); + + g_signal_connect(G_OBJECT(m_playbin), "notify::source", G_CALLBACK(playbinNotifySource), this); + g_signal_connect(G_OBJECT(m_playbin), "element-added", G_CALLBACK(handleElementAdded), this); + + // Initial volume + double volume = 1.0; + g_object_get(G_OBJECT(m_playbin), "volume", &volume, NULL); + m_volume = int(volume*100); + + g_signal_connect(G_OBJECT(m_playbin), "notify::volume", G_CALLBACK(handleVolumeChange), this); + if (m_usePlaybin2) + g_signal_connect(G_OBJECT(m_playbin), "notify::mute", G_CALLBACK(handleMutedChange), this); + } +} + +QGstreamerPlayerSession::~QGstreamerPlayerSession() +{ + if (m_playbin) { + stop(); + + delete m_busHelper; + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_playbin)); + gst_object_unref(GST_OBJECT(m_colorSpace)); + gst_object_unref(GST_OBJECT(m_nullVideoSink)); + gst_object_unref(GST_OBJECT(m_videoOutputBin)); + } +} + +#if defined(HAVE_GST_APPSRC) +void QGstreamerPlayerSession::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerPlayerSession* self) +{ + if (self->appsrc()->isReady()) + return; + + GstElement *appsrc; + g_object_get(orig, "source", &appsrc, NULL); + + if (!self->appsrc()->setup(appsrc)) + qWarning()<<"Could not setup appsrc element"; +} +#endif + +void QGstreamerPlayerSession::loadFromStream(const QNetworkRequest &request, QIODevice *appSrcStream) +{ +#if defined(HAVE_GST_APPSRC) + m_request = request; + m_duration = -1; + m_lastPosition = 0; + m_haveQueueElement = false; + + if (m_appSrc) + m_appSrc->deleteLater(); + m_appSrc = new QGstAppSrc(this); + m_appSrc->setStream(appSrcStream); + + if (m_playbin) { + m_tags.clear(); + emit tagsChanged(); + + g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerPlayerSession::configureAppSrcElement, (gpointer)this); + g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL); + + if (!m_streamTypes.isEmpty()) { + m_streamProperties.clear(); + m_streamTypes.clear(); + + emit streamsChanged(); + } + } +#endif +} + +void QGstreamerPlayerSession::loadFromUri(const QNetworkRequest &request) +{ + m_request = request; + m_duration = -1; + m_lastPosition = 0; + m_haveQueueElement = false; + + if (m_playbin) { + m_tags.clear(); + emit tagsChanged(); + + g_object_set(G_OBJECT(m_playbin), "uri", m_request.url().toEncoded().constData(), NULL); + + if (!m_streamTypes.isEmpty()) { + m_streamProperties.clear(); + m_streamTypes.clear(); + + emit streamsChanged(); + } + } +} + +qint64 QGstreamerPlayerSession::duration() const +{ + return m_duration; +} + +qint64 QGstreamerPlayerSession::position() const +{ + GstFormat format = GST_FORMAT_TIME; + gint64 position = 0; + + if ( m_playbin && gst_element_query_position(m_playbin, &format, &position)) + m_lastPosition = position / 1000000; + + return m_lastPosition; +} + +qreal QGstreamerPlayerSession::playbackRate() const +{ + return m_playbackRate; +} + +void QGstreamerPlayerSession::setPlaybackRate(qreal rate) +{ + if (!qFuzzyCompare(m_playbackRate, rate)) { + m_playbackRate = rate; + if (m_playbin) { + gst_element_seek(m_playbin, rate, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH), + GST_SEEK_TYPE_NONE,0, + GST_SEEK_TYPE_NONE,0 ); + } + emit playbackRateChanged(m_playbackRate); + } +} + +QMediaTimeRange QGstreamerPlayerSession::availablePlaybackRanges() const +{ + QMediaTimeRange ranges; +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 31) + //GST_FORMAT_TIME would be more appropriate, but unfortunately it's not supported. + //with GST_FORMAT_PERCENT media is treated as encoded with constant bitrate. + GstQuery* query = gst_query_new_buffering(GST_FORMAT_PERCENT); + + if (gst_element_query(m_playbin, query)) { + for (guint index = 0; index < gst_query_get_n_buffering_ranges(query); index++) { + gint64 rangeStart = 0; + gint64 rangeStop = 0; + + //This query should return values in GST_FORMAT_PERCENT_MAX range, + //but queue2 returns values in 0..100 range instead + if (gst_query_parse_nth_buffering_range(query, index, &rangeStart, &rangeStop)) + ranges.addInterval(rangeStart * duration() / 100, + rangeStop * duration() / 100); + } + } + + gst_query_unref(query); +#endif + + //without queue2 element in pipeline all the media is considered available + if (ranges.isEmpty() && duration() > 0 && !m_haveQueueElement) + ranges.addInterval(0, duration()); + +#ifdef DEBUG_PLAYBIN + qDebug() << ranges; +#endif + + return ranges; +} + +int QGstreamerPlayerSession::activeStream(QMediaStreamsControl::StreamType streamType) const +{ + int streamNumber = -1; + if (m_playbin) { + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_object_get(G_OBJECT(m_playbin), "current-audio", streamNumber, NULL); + break; + case QMediaStreamsControl::VideoStream: + g_object_get(G_OBJECT(m_playbin), "current-video", streamNumber, NULL); + break; + case QMediaStreamsControl::SubPictureStream: + g_object_get(G_OBJECT(m_playbin), "current-text", streamNumber, NULL); + break; + default: + break; + } + } + + if (m_usePlaybin2 && streamNumber >= 0) + streamNumber += m_playbin2StreamOffset.value(streamType,0); + + return streamNumber; +} + +void QGstreamerPlayerSession::setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber) +{ + + if (m_usePlaybin2 && streamNumber >= 0) + streamNumber -= m_playbin2StreamOffset.value(streamType,0); + + if (m_playbin) { + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_object_set(G_OBJECT(m_playbin), "current-audio", &streamNumber, NULL); + break; + case QMediaStreamsControl::VideoStream: + g_object_set(G_OBJECT(m_playbin), "current-video", &streamNumber, NULL); + break; + case QMediaStreamsControl::SubPictureStream: + g_object_set(G_OBJECT(m_playbin), "current-text", &streamNumber, NULL); + break; + default: + break; + } + } +} + + +bool QGstreamerPlayerSession::isBuffering() const +{ + return false; +} + +int QGstreamerPlayerSession::bufferingProgress() const +{ + return 0; +} + +int QGstreamerPlayerSession::volume() const +{ + return m_volume; +} + +bool QGstreamerPlayerSession::isMuted() const +{ + return m_muted; +} + +bool QGstreamerPlayerSession::isAudioAvailable() const +{ + return m_audioAvailable; +} + +static void block_pad_cb(GstPad *pad, gboolean blocked, gpointer user_data) +{ + Q_UNUSED(pad); +#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); + } +} + +void QGstreamerPlayerSession::updateVideoRenderer() +{ +#ifdef DEBUG_PLAYBIN + qDebug() << "Video sink has chaged, reload video output"; +#endif + + if (m_videoOutput) + setVideoRenderer(m_videoOutput); +} + +void QGstreamerPlayerSession::setVideoRenderer(QObject *videoOutput) +{ + if (m_videoOutput != videoOutput) { + if (m_videoOutput) { + disconnect(m_videoOutput, SIGNAL(sinkChanged()), + this, SLOT(updateVideoRenderer())); + disconnect(m_videoOutput, SIGNAL(readyChanged(bool)), + this, SLOT(updateVideoRenderer())); + } + + if (videoOutput) { + connect(videoOutput, SIGNAL(sinkChanged()), + this, SLOT(updateVideoRenderer())); + connect(videoOutput, SIGNAL(readyChanged(bool)), + this, SLOT(updateVideoRenderer())); + } + + m_videoOutput = videoOutput; + } + + QGstreamerVideoRendererInterface* renderer = qobject_cast<QGstreamerVideoRendererInterface*>(videoOutput); + + m_renderer = renderer; + +#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*/), + "playbin_set"); +#endif + + GstElement *videoSink = 0; + if (m_renderer && m_renderer->isReady()) + videoSink = m_renderer->videoSink(); + + if (!videoSink) + videoSink = m_nullVideoSink; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Set video output:" << videoOutput; + qDebug() << "Current sink:" << (m_videoSink ? GST_ELEMENT_NAME(m_videoSink) : "") << m_videoSink + << "pending:" << (m_pendingVideoSink ? GST_ELEMENT_NAME(m_pendingVideoSink) : "") << m_pendingVideoSink + << "new sink:" << (videoSink ? GST_ELEMENT_NAME(videoSink) : "") << videoSink; +#endif + + if (m_pendingVideoSink == videoSink || + (m_pendingVideoSink == 0 && m_videoSink == videoSink)) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Video sink has not changed, skip video output reconfiguration"; +#endif + return; + } + +#ifdef DEBUG_PLAYBIN + qDebug() << "Reconfigure video output"; +#endif + + if (m_state == QMediaPlayer::StoppedState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "The pipeline has not started yet, pending state:" << m_pendingState; +#endif + //the pipeline has not started yet + m_pendingVideoSink = 0; + gst_element_set_state(m_videoSink, GST_STATE_NULL); + gst_element_set_state(m_playbin, GST_STATE_NULL); + + 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); + } + + gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink); + + m_videoSink = videoSink; + + gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink); + + m_usingColorspaceElement = false; + bool linked = gst_element_link(m_videoIdentity, m_videoSink); + if (!linked) { + m_usingColorspaceElement = true; +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace element."; +#endif + gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace); + linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL); + } + + switch (m_pendingState) { + case QMediaPlayer::PausedState: + gst_element_set_state(m_playbin, GST_STATE_PAUSED); + break; + case QMediaPlayer::PlayingState: + gst_element_set_state(m_playbin, GST_STATE_PLAYING); + break; + default: + break; + } + } else { + if (m_pendingVideoSink) { +#ifdef DEBUG_PLAYBIN + qDebug() << "already waiting for pad to be blocked, just change the pending sink"; +#endif + m_pendingVideoSink = videoSink; + return; + } + + m_pendingVideoSink = videoSink; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Blocking the video output pad..."; +#endif + + //block pads, async to avoid locking in paused state + GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src"); + gst_pad_set_blocked_async(srcPad, true, &block_pad_cb, this); + gst_object_unref(GST_OBJECT(srcPad)); + + //Unpause the sink to avoid waiting until the buffer is processed + //while the sink is paused. The pad will be blocked as soon as the current + //buffer is processed. + if (m_state == QMediaPlayer::PausedState) { +#ifdef DEBUG_PLAYBIN + qDebug() << "Starting video output to avoid blocking in paused state..."; +#endif + gst_element_set_state(m_videoSink, GST_STATE_PLAYING); + } + } +} + +void QGstreamerPlayerSession::finishVideoOutputChange() +{ + if (!m_pendingVideoSink) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << "finishVideoOutputChange" << m_pendingVideoSink; +#endif + + GstPad *srcPad = gst_element_get_static_pad(m_videoIdentity, "src"); + + if (!gst_pad_is_blocked(srcPad)) { + //pad is not blocked, it's possible to swap outputs only in the null state + qWarning() << "Pad is not blocked yet, could not switch video sink"; + GstState identityElementState = GST_STATE_NULL; + gst_element_get_state(m_videoIdentity, &identityElementState, NULL, GST_CLOCK_TIME_NONE); + if (identityElementState != GST_STATE_NULL) { + gst_object_unref(GST_OBJECT(srcPad)); + return; //can't change vo yet, received async call from the previous change + } + } + + if (m_pendingVideoSink == m_videoSink) { + //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)) + gst_pad_set_blocked_async(srcPad, false, &block_pad_cb, 0); + + m_pendingVideoSink = 0; + gst_object_unref(GST_OBJECT(srcPad)); + return; + } + + if (m_usingColorspaceElement) { + gst_element_set_state(m_colorSpace, GST_STATE_NULL); + gst_element_set_state(m_videoSink, GST_STATE_NULL); + + gst_element_unlink(m_colorSpace, m_videoSink); + gst_bin_remove(GST_BIN(m_videoOutputBin), m_colorSpace); + } else { + gst_element_set_state(m_videoSink, GST_STATE_NULL); + gst_element_unlink(m_videoIdentity, m_videoSink); + } + + gst_bin_remove(GST_BIN(m_videoOutputBin), m_videoSink); + + m_videoSink = m_pendingVideoSink; + m_pendingVideoSink = 0; + + gst_bin_add(GST_BIN(m_videoOutputBin), m_videoSink); + + m_usingColorspaceElement = false; + bool linked = gst_element_link(m_videoIdentity, m_videoSink); + if (!linked) { + m_usingColorspaceElement = true; +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace element."; +#endif + gst_bin_add(GST_BIN(m_videoOutputBin), m_colorSpace); + linked = gst_element_link_many(m_videoIdentity, m_colorSpace, m_videoSink, NULL); + } + + if (!linked) + qWarning() << "Linking video output element failed"; + +#ifdef DEBUG_PLAYBIN + qDebug() << "notify the video connector it has to emit a new segment message..."; +#endif + //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, + "resend-new-segment", + true //emit connection-failed signal + //to have a chance to insert colorspace element + ); + + + GstState state; + + switch (m_pendingState) { + case QMediaPlayer::StoppedState: + state = GST_STATE_NULL; + break; + case QMediaPlayer::PausedState: + state = GST_STATE_PAUSED; + break; + case QMediaPlayer::PlayingState: + state = GST_STATE_PLAYING; + break; + } + + if (m_usingColorspaceElement) + gst_element_set_state(m_colorSpace, state); + + gst_element_set_state(m_videoSink, state); + + // Set state change that was deferred due the video output + // change being pending + gst_element_set_state(m_playbin, state); + + //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); + 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*/), + "playbin_finish"); +#endif +} + +void QGstreamerPlayerSession::insertColorSpaceElement(GstElement *element, gpointer data) +{ + Q_UNUSED(element); + QGstreamerPlayerSession* session = reinterpret_cast<QGstreamerPlayerSession*>(data); + + if (session->m_usingColorspaceElement) + return; + session->m_usingColorspaceElement = true; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Failed to connect video output, inserting the colorspace elemnt."; + qDebug() << "notify the video connector it has to emit a new segment message..."; +#endif + //it's necessary to send a new segment event just before + //the first buffer pushed to the new sink + g_signal_emit_by_name(session->m_videoIdentity, + "resend-new-segment", + false // don't emit connection-failed signal + ); + + gst_element_unlink(session->m_videoIdentity, session->m_videoSink); + gst_bin_add(GST_BIN(session->m_videoOutputBin), session->m_colorSpace); + gst_element_link_many(session->m_videoIdentity, session->m_colorSpace, session->m_videoSink, NULL); + + GstState state; + + switch (session->m_pendingState) { + case QMediaPlayer::StoppedState: + state = GST_STATE_NULL; + break; + case QMediaPlayer::PausedState: + state = GST_STATE_PAUSED; + break; + case QMediaPlayer::PlayingState: + state = GST_STATE_PLAYING; + break; + } + + gst_element_set_state(session->m_colorSpace, state); +} + + +bool QGstreamerPlayerSession::isVideoAvailable() const +{ + return m_videoAvailable; +} + +bool QGstreamerPlayerSession::isSeekable() const +{ + return m_seekable; +} + +bool QGstreamerPlayerSession::play() +{ + m_everPlayed = false; + if (m_playbin) { + m_pendingState = QMediaPlayer::PlayingState; + if (gst_element_set_state(m_playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to play -" << m_request.url().toString(); + m_pendingState = m_state = QMediaPlayer::StoppedState; + + emit stateChanged(m_state); + } else + return true; + } + + return false; +} + +bool QGstreamerPlayerSession::pause() +{ + if (m_playbin) { + m_pendingState = QMediaPlayer::PausedState; + if (m_pendingVideoSink != 0) + return true; + + if (gst_element_set_state(m_playbin, GST_STATE_PAUSED) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to pause -" << m_request.url().toString(); + m_pendingState = m_state = QMediaPlayer::StoppedState; + + emit stateChanged(m_state); + } else { + return true; + } + } + + return false; +} + +void QGstreamerPlayerSession::stop() +{ + m_everPlayed = false; + if (m_playbin) { + if (m_renderer) + m_renderer->stopRenderer(); + + gst_element_set_state(m_playbin, GST_STATE_NULL); + + m_lastPosition = 0; + QMediaPlayer::State oldState = m_state; + m_pendingState = m_state = QMediaPlayer::StoppedState; + + finishVideoOutputChange(); + + //we have to do it here, since gstreamer will not emit bus messages any more + setSeekable(false); + if (oldState != m_state) + emit stateChanged(m_state); + } +} + +bool QGstreamerPlayerSession::seek(qint64 ms) +{ + //seek locks when the video output sink is changing and pad is blocked + if (m_playbin && !m_pendingVideoSink && m_state != QMediaPlayer::StoppedState) { + ms = qMax(ms,qint64(0)); + gint64 position = ms * 1000000; + bool isSeeking = gst_element_seek(m_playbin, + m_playbackRate, + GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH), + GST_SEEK_TYPE_SET, + position, + GST_SEEK_TYPE_NONE, + 0); + if (isSeeking) + m_lastPosition = ms; + + return isSeeking; + } + + return false; +} + +void QGstreamerPlayerSession::setVolume(int volume) +{ + if (m_volume != volume) { + m_volume = volume; + + if (m_playbin) { + //playbin2 allows to set volume and muted independently, + //with playbin1 it's necessary to keep volume at 0 while muted + if (!m_muted || m_usePlaybin2) + g_object_set(G_OBJECT(m_playbin), "volume", m_volume/100.0, NULL); + } + + emit volumeChanged(m_volume); + } +} + +void QGstreamerPlayerSession::setMuted(bool muted) +{ + if (m_muted != muted) { + m_muted = muted; + + if (m_usePlaybin2) + g_object_set(G_OBJECT(m_playbin), "mute", m_muted, NULL); + else + g_object_set(G_OBJECT(m_playbin), "volume", (m_muted ? 0 : m_volume/100.0), NULL); + + emit mutedStateChanged(m_muted); + } +} + + +void QGstreamerPlayerSession::setSeekable(bool seekable) +{ + if (seekable != m_seekable) { + m_seekable = seekable; + emit seekableChanged(m_seekable); + } +} + +bool QGstreamerPlayerSession::processSyncMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm && GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT) { + if (m_renderer) { + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) + m_renderer->handleSyncMessage(gm); + + if (gst_structure_has_name(gm->structure, "prepare-xwindow-id")) { + m_renderer->precessNewStream(); + return true; + } + } + } + + return false; +} + +void QGstreamerPlayerSession::busMessage(const QGstreamerMessage &message) +{ + GstMessage* gm = message.rawMessage(); + + if (gm) { + //tag message comes from elements inside playbin, not from playbin itself + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_TAG) { + //qDebug() << "tag message"; + GstTagList *tag_list; + gst_message_parse_tag(gm, &tag_list); + m_tags.unite(QGstUtils::gstTagListToMap(tag_list)); + + //qDebug() << m_tags; + + emit tagsChanged(); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) { + updateDuration(); + } + +#ifdef DEBUG_PLAYBIN + if (m_sourceType == MMSSrc && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) { + qDebug() << "Message from MMSSrc: " << GST_MESSAGE_TYPE(gm); + } +#endif + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_BUFFERING) { + int progress = 0; + gst_message_parse_buffering(gm, &progress); + emit bufferingProgressChanged(progress); + } + + bool handlePlaybin2 = false; + if (GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_playbin)) { + switch (GST_MESSAGE_TYPE(gm)) { + case GST_MESSAGE_STATE_CHANGED: + { + GstState oldState; + GstState newState; + GstState pending; + + gst_message_parse_state_changed(gm, &oldState, &newState, &pending); + +#ifdef DEBUG_PLAYBIN + QStringList states; + states << "GST_STATE_VOID_PENDING" << "GST_STATE_NULL" << "GST_STATE_READY" << "GST_STATE_PAUSED" << "GST_STATE_PLAYING"; + + qDebug() << QString("state changed: old: %1 new: %2 pending: %3") \ + .arg(states[oldState]) \ + .arg(states[newState]) \ + .arg(states[pending]); +#endif + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + setSeekable(false); + finishVideoOutputChange(); + if (m_state != QMediaPlayer::StoppedState) + emit stateChanged(m_state = QMediaPlayer::StoppedState); + break; + case GST_STATE_READY: + setSeekable(false); + if (m_state != QMediaPlayer::StoppedState) + emit stateChanged(m_state = QMediaPlayer::StoppedState); + break; + case GST_STATE_PAUSED: + { + QMediaPlayer::State prevState = m_state; + m_state = QMediaPlayer::PausedState; + + //check for seekable + if (oldState == GST_STATE_READY) { + if (m_sourceType == SoupHTTPSrc || m_sourceType == MMSSrc) { + //since udpsrc is a live source, it is not applicable here + m_everPlayed = true; + } + + getStreamsInfo(); + updateVideoResolutionTag(); + + //gstreamer doesn't give a reliable indication the duration + //information is ready, GST_MESSAGE_DURATION is not sent by most elements + //the duration is queried up to 5 times with increasing delay + m_durationQueries = 5; + updateDuration(); + + /* + //gst_element_seek_simple doesn't work reliably here, have to find a better solution + + GstFormat format = GST_FORMAT_TIME; + gint64 position = 0; + bool seekable = false; + if (gst_element_query_position(m_playbin, &format, &position)) { + seekable = gst_element_seek_simple(m_playbin, format, GST_SEEK_FLAG_NONE, position); + } + + setSeekable(seekable); + */ + + setSeekable(true); + + if (!qFuzzyCompare(m_playbackRate, qreal(1.0))) { + qreal rate = m_playbackRate; + m_playbackRate = 1.0; + setPlaybackRate(rate); + } + } + + if (m_state != prevState) + emit stateChanged(m_state); + + break; + } + case GST_STATE_PLAYING: + m_everPlayed = true; + if (m_state != QMediaPlayer::PlayingState) + emit stateChanged(m_state = QMediaPlayer::PlayingState); + + break; + } + } + break; + + case GST_MESSAGE_EOS: + emit playbackFinished(); + break; + + case GST_MESSAGE_TAG: + case GST_MESSAGE_STREAM_STATUS: + case GST_MESSAGE_UNKNOWN: + break; + case GST_MESSAGE_ERROR: { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) + processInvalidMedia(QMediaPlayer::FormatError, tr("Cannot play stream of type: <unknown>")); + else + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } + break; + case GST_MESSAGE_WARNING: + { + GError *err; + gchar *debug; + gst_message_parse_warning (gm, &err, &debug); + qWarning() << "Warning:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } + break; + case GST_MESSAGE_INFO: +#ifdef DEBUG_PLAYBIN + { + GError *err; + gchar *debug; + gst_message_parse_info (gm, &err, &debug); + qDebug() << "Info:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } +#endif + break; + case GST_MESSAGE_BUFFERING: + case GST_MESSAGE_STATE_DIRTY: + case GST_MESSAGE_STEP_DONE: + case GST_MESSAGE_CLOCK_PROVIDE: + case GST_MESSAGE_CLOCK_LOST: + case GST_MESSAGE_NEW_CLOCK: + case GST_MESSAGE_STRUCTURE_CHANGE: + case GST_MESSAGE_APPLICATION: + case GST_MESSAGE_ELEMENT: + break; + case GST_MESSAGE_SEGMENT_START: + { + const GstStructure *structure = gst_message_get_structure(gm); + qint64 position = g_value_get_int64(gst_structure_get_value(structure, "position")); + position /= 1000000; + m_lastPosition = position; + emit positionChanged(position); + } + break; + case GST_MESSAGE_SEGMENT_DONE: + break; + case GST_MESSAGE_LATENCY: +#if (GST_VERSION_MAJOR >= 0) && (GST_VERSION_MINOR >= 10) && (GST_VERSION_MICRO >= 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)) { + position /= 1000000; + m_lastPosition = position; + emit positionChanged(position); + } + break; + } +#if GST_VERSION_MICRO >= 23 + case GST_MESSAGE_REQUEST_STATE: +#endif +#endif + case GST_MESSAGE_ANY: + break; + default: + break; + } + } else if (m_videoSink + && m_renderer + && GST_MESSAGE_SRC(gm) == GST_OBJECT_CAST(m_videoSink)) { + + m_renderer->handleBusMessage(gm); + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) { + GstState oldState; + GstState newState; + gst_message_parse_state_changed(gm, &oldState, &newState, 0); + + if (oldState == GST_STATE_READY && newState == GST_STATE_PAUSED) + m_renderer->precessNewStream(); + } + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + // If the source has given up, so do we. + if (qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0) { + bool everPlayed = m_everPlayed; + // Try and differentiate network related resource errors from the others + if (!m_request.url().isRelative() && m_request.url().scheme().compare(QLatin1String("file"), Qt::CaseInsensitive) != 0 ) { + if (everPlayed || + (err->domain == GST_RESOURCE_ERROR && ( + err->code == GST_RESOURCE_ERROR_BUSY || + err->code == GST_RESOURCE_ERROR_OPEN_READ || + err->code == GST_RESOURCE_ERROR_READ || + err->code == GST_RESOURCE_ERROR_SEEK || + err->code == GST_RESOURCE_ERROR_SYNC))) { + processInvalidMedia(QMediaPlayer::NetworkError, QString::fromUtf8(err->message)); + } else { + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } + } + else + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } else if (err->domain == GST_STREAM_ERROR + && (err->code == GST_STREAM_ERROR_DECRYPT || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) { + processInvalidMedia(QMediaPlayer::AccessDeniedError, QString::fromUtf8(err->message)); + } else { + handlePlaybin2 = m_usePlaybin2; + } + if (!handlePlaybin2) + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT + && qstrcmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "source") == 0 + && m_sourceType == UDPSrc + && gst_structure_has_name(gst_message_get_structure(gm), "GstUDPSrcTimeout")) { + //since udpsrc will not generate an error for the timeout event, + //we need to process its element message here and treat it as an error. + processInvalidMedia(m_everPlayed ? QMediaPlayer::NetworkError : QMediaPlayer::ResourceError, + tr("UDP source timeout")); + } else { + handlePlaybin2 = m_usePlaybin2; + } + + if (handlePlaybin2) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_WARNING) { + GError *err; + gchar *debug; + gst_message_parse_warning(gm, &err, &debug); + if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) + emit error(int(QMediaPlayer::FormatError), tr("Cannot play stream of type: <unknown>")); + qWarning() << "Warning:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + if (qstrncmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "decodebin2", 10) == 0 + || qstrncmp(GST_OBJECT_NAME(GST_MESSAGE_SRC(gm)), "uridecodebin", 12) == 0) { + processInvalidMedia(QMediaPlayer::ResourceError, QString::fromUtf8(err->message)); + } else if (err->domain == GST_STREAM_ERROR + && (err->code == GST_STREAM_ERROR_DECRYPT || err->code == GST_STREAM_ERROR_DECRYPT_NOKEY)) { + processInvalidMedia(QMediaPlayer::AccessDeniedError, QString::fromUtf8(err->message)); + } + qWarning() << "Error:" << QString::fromUtf8(err->message); + g_error_free(err); + g_free(debug); + } + } + } +} + +void QGstreamerPlayerSession::getStreamsInfo() +{ + //check if video is available: + bool haveAudio = false; + bool haveVideo = false; + m_streamProperties.clear(); + m_streamTypes.clear(); + + if (m_usePlaybin2) { + gint audioStreamsCount = 0; + gint videoStreamsCount = 0; + gint textStreamsCount = 0; + + g_object_get(G_OBJECT(m_playbin), "n-audio", &audioStreamsCount, NULL); + g_object_get(G_OBJECT(m_playbin), "n-video", &videoStreamsCount, NULL); + g_object_get(G_OBJECT(m_playbin), "n-text", &textStreamsCount, NULL); + + haveAudio = audioStreamsCount > 0; + haveVideo = videoStreamsCount > 0; + + m_playbin2StreamOffset[QMediaStreamsControl::AudioStream] = 0; + m_playbin2StreamOffset[QMediaStreamsControl::VideoStream] = audioStreamsCount; + m_playbin2StreamOffset[QMediaStreamsControl::SubPictureStream] = audioStreamsCount+videoStreamsCount; + + for (int i=0; i<audioStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::AudioStream); + + for (int i=0; i<videoStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::VideoStream); + + for (int i=0; i<textStreamsCount; i++) + m_streamTypes.append(QMediaStreamsControl::SubPictureStream); + + for (int i=0; i<m_streamTypes.count(); i++) { + QMediaStreamsControl::StreamType streamType = m_streamTypes[i]; + QMap<QtMultimediaKit::MetaData, QVariant> streamProperties; + + int streamIndex = i - m_playbin2StreamOffset[streamType]; + + GstTagList *tags = 0; + switch (streamType) { + case QMediaStreamsControl::AudioStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-audio-tags", streamIndex, &tags); + break; + case QMediaStreamsControl::VideoStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-video-tags", streamIndex, &tags); + break; + case QMediaStreamsControl::SubPictureStream: + g_signal_emit_by_name(G_OBJECT(m_playbin), "get-text-tags", streamIndex, &tags); + break; + default: + break; + } + + if (tags && gst_is_tag_list(tags)) { + gchar *languageCode = 0; + if (gst_tag_list_get_string(tags, GST_TAG_LANGUAGE_CODE, &languageCode)) + streamProperties[QtMultimediaKit::Language] = QString::fromUtf8(languageCode); + + //qDebug() << "language for setream" << i << QString::fromUtf8(languageCode); + g_free (languageCode); + } + + m_streamProperties.append(streamProperties); + } + } else { // PlayBin 1 + enum { + GST_STREAM_TYPE_UNKNOWN, + GST_STREAM_TYPE_AUDIO, + GST_STREAM_TYPE_VIDEO, + GST_STREAM_TYPE_TEXT, + GST_STREAM_TYPE_SUBPICTURE, + GST_STREAM_TYPE_ELEMENT + }; + + GList* streamInfoList; + g_object_get(G_OBJECT(m_playbin), "stream-info", &streamInfoList, NULL); + + for (; streamInfoList != 0; streamInfoList = g_list_next(streamInfoList)) { + gint type; + gchar *languageCode = 0; + + GObject* streamInfo = G_OBJECT(streamInfoList->data); + + g_object_get(streamInfo, "type", &type, NULL); + g_object_get(streamInfo, "language-code", &languageCode, NULL); + + QMediaStreamsControl::StreamType streamType = QMediaStreamsControl::UnknownStream; + + switch (type) { + case GST_STREAM_TYPE_VIDEO: + streamType = QMediaStreamsControl::VideoStream; + haveVideo = true; + break; + case GST_STREAM_TYPE_AUDIO: + streamType = QMediaStreamsControl::AudioStream; + haveAudio = true; + break; + case GST_STREAM_TYPE_SUBPICTURE: + streamType = QMediaStreamsControl::SubPictureStream; + break; + case GST_STREAM_TYPE_UNKNOWN: { + GstCaps *caps = 0; + g_object_get(streamInfo, "caps", &caps, NULL); + const GstStructure *structure = gst_caps_get_structure(caps, 0); + const gchar *media_type = gst_structure_get_name(structure); + emit error(int(QMediaPlayer::FormatError), QString::fromLatin1("Cannot play stream of type: %1").arg(QString::fromUtf8(media_type))); +#ifdef DEBUG_PLAYBIN + qDebug() << "Encountered unknown stream type"; +#endif + gst_caps_unref(caps); + } + default: + streamType = QMediaStreamsControl::UnknownStream; + break; + } + + QMap<QtMultimediaKit::MetaData, QVariant> streamProperties; + streamProperties[QtMultimediaKit::Language] = QString::fromUtf8(languageCode); + + m_streamProperties.append(streamProperties); + m_streamTypes.append(streamType); + } + } + + + if (haveAudio != m_audioAvailable) { + m_audioAvailable = haveAudio; + emit audioAvailableChanged(m_audioAvailable); + } + if (haveVideo != m_videoAvailable) { + m_videoAvailable = haveVideo; + emit videoAvailableChanged(m_videoAvailable); + } + + emit streamsChanged(); +} + +void QGstreamerPlayerSession::updateVideoResolutionTag() +{ + QSize size; + QSize aspectRatio; + + GstPad *pad = gst_element_get_static_pad(m_videoIdentity, "src"); + GstCaps *caps = gst_pad_get_negotiated_caps(pad); + + if (caps) { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + gst_structure_get_int(structure, "width", &size.rwidth()); + gst_structure_get_int(structure, "height", &size.rheight()); + + gint aspectNum = 0; + gint aspectDenum = 0; + if (!size.isEmpty() && gst_structure_get_fraction( + structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { + if (aspectDenum > 0) + aspectRatio = QSize(aspectNum, aspectDenum); + } + gst_caps_unref(caps); + } + + gst_object_unref(GST_OBJECT(pad)); + + QSize currentSize = m_tags.value("resolution").toSize(); + QSize currentAspectRatio = m_tags.value("pixel-aspect-ratio").toSize(); + + if (currentSize != size || currentAspectRatio != aspectRatio) { + if (aspectRatio.isEmpty()) + m_tags.remove("pixel-aspect-ratio"); + + if (size.isEmpty()) { + m_tags.remove("resolution"); + } else { + m_tags.insert("resolution", QVariant(size)); + if (!aspectRatio.isEmpty()) + m_tags.insert("pixel-aspect-ratio", QVariant(aspectRatio)); + } + + emit tagsChanged(); + } +} + +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)) + duration = gstDuration / 1000000; + + if (m_duration != duration) { + m_duration = duration; + emit durationChanged(m_duration); + } + + if (m_duration > 0) + m_durationQueries = 0; + + if (m_durationQueries > 0) { + //increase delay between duration requests + int delay = 25 << (5 - m_durationQueries); + QTimer::singleShot(delay, this, SLOT(updateDuration())); + m_durationQueries--; + } +} + +void QGstreamerPlayerSession::playbinNotifySource(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(p); + + GstElement *source = 0; + g_object_get(o, "source", &source, NULL); + if (source == 0) + return; + +#ifdef DEBUG_PLAYBIN + qDebug() << "Playbin source added:" << G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)); +#endif + + // Turn off icecast metadata request, will be re-set if in QNetworkRequest + // (souphttpsrc docs say is false by default, but header appears in request + // @version 0.10.21) + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "iradio-mode") != 0) + g_object_set(G_OBJECT(source), "iradio-mode", FALSE, NULL); + + + // Set Headers + const QByteArray userAgentString("User-Agent"); + + QGstreamerPlayerSession *self = reinterpret_cast<QGstreamerPlayerSession *>(d); + + // User-Agent - special case, souphhtpsrc will always set something, even if + // defined in extra-headers + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "user-agent") != 0) { + g_object_set(G_OBJECT(source), "user-agent", + self->m_request.rawHeader(userAgentString).constData(), NULL); + } + + // The rest + if (g_object_class_find_property(G_OBJECT_GET_CLASS(source), "extra-headers") != 0) { + GstStructure *extras = gst_structure_empty_new("extras"); + + foreach (const QByteArray &rawHeader, self->m_request.rawHeaderList()) { + if (rawHeader == userAgentString) // Filter User-Agent + continue; + else { + GValue headerValue; + + memset(&headerValue, 0, sizeof(GValue)); + g_value_init(&headerValue, G_TYPE_STRING); + + g_value_set_string(&headerValue, + self->m_request.rawHeader(rawHeader).constData()); + + gst_structure_set_value(extras, rawHeader.constData(), &headerValue); + } + } + + if (gst_structure_n_fields(extras) > 0) + g_object_set(G_OBJECT(source), "extra-headers", extras, NULL); + + gst_structure_free(extras); + } + + //set timeout property to 5 seconds + if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstUDPSrc") == 0) { + //udpsrc timeout unit = microsecond + g_object_set(G_OBJECT(source), "timeout", G_GUINT64_CONSTANT(5000000), NULL); + self->m_sourceType = UDPSrc; + } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstSoupHTTPSrc") == 0) { + //souphttpsrc timeout unit = second + g_object_set(G_OBJECT(source), "timeout", guint(5), NULL); + self->m_sourceType = SoupHTTPSrc; + } else if (qstrcmp(G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(source)), "GstMMSSrc") == 0) { + self->m_sourceType = MMSSrc; + g_object_set(G_OBJECT(source), "tcp-timeout", G_GUINT64_CONSTANT(5000000), NULL); + } else { + self->m_sourceType = UnknownSrc; + } + + gst_object_unref(source); +} + +void QGstreamerPlayerSession::handleVolumeChange(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(o); + Q_UNUSED(p); + QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d); + QMetaObject::invokeMethod(session, "updateVolume", Qt::QueuedConnection); +} + +void QGstreamerPlayerSession::updateVolume() +{ + double volume = 1.0; + g_object_get(m_playbin, "volume", &volume, NULL); + + //special case for playbin1 volume changes in muted state + //playbin1 has no separate muted state, + //it's emulated with volume value saved and set to 0 + //this change should not be reported to user + if (!m_usePlaybin2 && m_muted) { + if (volume > 0.001) { + //volume is changed, player in not muted any more + m_muted = false; + emit mutedStateChanged(m_muted = false); + } else { + //don't emit volume changed to 0 when player is muted + return; + } + } + + if (m_volume != int(volume*100)) { + m_volume = int(volume*100); +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << m_muted; +#endif + emit volumeChanged(m_volume); + } +} + +void QGstreamerPlayerSession::handleMutedChange(GObject *o, GParamSpec *p, gpointer d) +{ + Q_UNUSED(o); + Q_UNUSED(p); + QGstreamerPlayerSession *session = reinterpret_cast<QGstreamerPlayerSession *>(d); + QMetaObject::invokeMethod(session, "updateMuted", Qt::QueuedConnection); +} + +void QGstreamerPlayerSession::updateMuted() +{ + gboolean muted = false; + g_object_get(G_OBJECT(m_playbin), "mute", &muted, NULL); + if (m_muted != muted) { + m_muted = muted; +#ifdef DEBUG_PLAYBIN + qDebug() << Q_FUNC_INFO << m_muted; +#endif + emit mutedStateChanged(muted); + } +} + + +void QGstreamerPlayerSession::handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session) +{ + Q_UNUSED(bin); + //we have to configure queue2 element to enable media downloading + //and reporting available ranges, + //but it's added dynamically to playbin2 + + gchar *elementName = gst_element_get_name(element); + + if (g_str_has_prefix(elementName, "queue2")) { + session->m_haveQueueElement = true; + + if (session->property("mediaDownloadEnabled").toBool()) { + QDir cacheDir(QDesktopServices::storageLocation(QDesktopServices::CacheLocation)); + QString cacheLocation = cacheDir.absoluteFilePath("gstmedia__XXXXXX"); +#ifdef DEBUG_PLAYBIN + qDebug() << "set queue2 temp-location" << cacheLocation; +#endif + g_object_set(G_OBJECT(element), "temp-template", cacheLocation.toUtf8().constData(), NULL); + } else { + g_object_set(G_OBJECT(element), "temp-template", NULL, NULL); + } + } else if (g_str_has_prefix(elementName, "uridecodebin") || + g_str_has_prefix(elementName, "decodebin2")) { + //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", + G_CALLBACK(handleElementAdded), session); + } + + g_free(elementName); +} + +//doing proper operations when detecting an invalidMedia: change media status before signal the erorr +void QGstreamerPlayerSession::processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString) +{ + emit invalidMedia(); + stop(); + emit error(int(errorCode), errorString); +} diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h new file mode 100644 index 000000000..e6fa996b0 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerplayersession.h @@ -0,0 +1,217 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERPLAYERSESSION_H +#define QGSTREAMERPLAYERSESSION_H + +#include <QObject> +#include <QtNetwork/qnetworkrequest.h> +#include "qgstreamerplayercontrol.h" +#include "qgstreamerbushelper.h" +#include <qmediaplayer.h> +#include <qmediastreamscontrol.h> + +#if defined(HAVE_GST_APPSRC) +#include "qgstappsrc.h" +#endif + +#include <gst/gst.h> + +class QGstreamerBusHelper; +class QGstreamerMessage; + +class QGstreamerVideoRendererInterface; + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession : public QObject, public QGstreamerSyncEventFilter +{ +Q_OBJECT + +public: + QGstreamerPlayerSession(QObject *parent); + virtual ~QGstreamerPlayerSession(); + + QNetworkRequest request() const; + + QMediaPlayer::State state() const { return m_state; } + QMediaPlayer::State pendingState() const { return m_pendingState; } + + qint64 duration() const; + qint64 position() const; + + bool isBuffering() const; + + int bufferingProgress() const; + + int volume() const; + bool isMuted() const; + + bool isAudioAvailable() const; + + void setVideoRenderer(QObject *renderer); + bool isVideoAvailable() const; + + bool isSeekable() const; + + qreal playbackRate() const; + void setPlaybackRate(qreal rate); + + QMediaTimeRange availablePlaybackRanges() const; + + QMap<QByteArray ,QVariant> tags() const { return m_tags; } + QMap<QtMultimediaKit::MetaData,QVariant> streamProperties(int streamNumber) const { return m_streamProperties[streamNumber]; } + int streamCount() const { return m_streamProperties.count(); } + QMediaStreamsControl::StreamType streamType(int streamNumber) { return m_streamTypes.value(streamNumber, QMediaStreamsControl::UnknownStream); } + + int activeStream(QMediaStreamsControl::StreamType streamType) const; + void setActiveStream(QMediaStreamsControl::StreamType streamType, int streamNumber); + + bool processSyncMessage(const QGstreamerMessage &message); + +#if defined(HAVE_GST_APPSRC) + QGstAppSrc *appsrc() const { return m_appSrc; } + static void configureAppSrcElement(GObject*, GObject*, GParamSpec*,QGstreamerPlayerSession* _this); +#endif + +public slots: + void loadFromUri(const QNetworkRequest &url); + void loadFromStream(const QNetworkRequest &url, QIODevice *stream); + bool play(); + bool pause(); + void stop(); + + bool seek(qint64 pos); + + void setVolume(int volume); + void setMuted(bool muted); + +signals: + void durationChanged(qint64 duration); + void positionChanged(qint64 position); + void stateChanged(QMediaPlayer::State state); + void volumeChanged(int volume); + void mutedStateChanged(bool muted); + void audioAvailableChanged(bool audioAvailable); + void videoAvailableChanged(bool videoAvailable); + void bufferingChanged(bool buffering); + void bufferingProgressChanged(int percentFilled); + void playbackFinished(); + void tagsChanged(); + void streamsChanged(); + void seekableChanged(bool); + void error(int error, const QString &errorString); + void invalidMedia(); + void playbackRateChanged(qreal); + +private slots: + void busMessage(const QGstreamerMessage &message); + void getStreamsInfo(); + void setSeekable(bool); + void finishVideoOutputChange(); + void updateVideoRenderer(); + void updateVideoResolutionTag(); + void updateVolume(); + void updateMuted(); + void updateDuration(); + +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); + static void insertColorSpaceElement(GstElement *element, gpointer data); + static void handleElementAdded(GstBin *bin, GstElement *element, QGstreamerPlayerSession *session); + void processInvalidMedia(QMediaPlayer::Error errorCode, const QString& errorString); + + QNetworkRequest m_request; + QMediaPlayer::State m_state; + QMediaPlayer::State m_pendingState; + QGstreamerBusHelper* m_busHelper; + GstElement* m_playbin; + bool m_usePlaybin2; + + GstElement* m_videoOutputBin; + GstElement* m_videoIdentity; + GstElement* m_colorSpace; + bool m_usingColorspaceElement; + GstElement* m_videoSink; + GstElement* m_pendingVideoSink; + GstElement* m_nullVideoSink; + + GstBus* m_bus; + QObject *m_videoOutput; + QGstreamerVideoRendererInterface *m_renderer; + + bool m_haveQueueElement; + +#if defined(HAVE_GST_APPSRC) + QGstAppSrc *m_appSrc; +#endif + + QMap<QByteArray, QVariant> m_tags; + QList< QMap<QtMultimediaKit::MetaData,QVariant> > m_streamProperties; + QList<QMediaStreamsControl::StreamType> m_streamTypes; + QMap<QMediaStreamsControl::StreamType, int> m_playbin2StreamOffset; + + + int m_volume; + qreal m_playbackRate; + bool m_muted; + bool m_audioAvailable; + bool m_videoAvailable; + bool m_seekable; + + mutable qint64 m_lastPosition; + qint64 m_duration; + int m_durationQueries; + + enum SourceType + { + UnknownSrc, + SoupHTTPSrc, + UDPSrc, + MMSSrc + }; + SourceType m_sourceType; + bool m_everPlayed; +}; + +#endif // QGSTREAMERPLAYERSESSION_H diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp new file mode 100644 index 000000000..0ab93022b --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.cpp @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerstreamscontrol.h" +#include "qgstreamerplayersession.h" + +QGstreamerStreamsControl::QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent) + :QMediaStreamsControl(parent), m_session(session) +{ + connect(m_session, SIGNAL(streamsChanged()), SIGNAL(streamsChanged())); +} + +QGstreamerStreamsControl::~QGstreamerStreamsControl() +{ +} + +int QGstreamerStreamsControl::streamCount() +{ + return m_session->streamCount(); +} + +QMediaStreamsControl::StreamType QGstreamerStreamsControl::streamType(int streamNumber) +{ + return m_session->streamType(streamNumber); +} + +QVariant QGstreamerStreamsControl::metaData(int streamNumber, QtMultimediaKit::MetaData key) +{ + return m_session->streamProperties(streamNumber).value(key); +} + +bool QGstreamerStreamsControl::isActive(int streamNumber) +{ + return streamNumber != -1 && streamNumber == m_session->activeStream(streamType(streamNumber)); +} + +void QGstreamerStreamsControl::setActive(int streamNumber, bool state) +{ + QMediaStreamsControl::StreamType type = m_session->streamType(streamNumber); + if (type == QMediaStreamsControl::UnknownStream) + return; + + if (state) + m_session->setActiveStream(type, streamNumber); + else { + //only one active stream of certain type is supported + if (m_session->activeStream(type) == streamNumber) + m_session->setActiveStream(type, -1); + } +} + diff --git a/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h new file mode 100644 index 000000000..1213455b9 --- /dev/null +++ b/src/plugins/gstreamer/mediaplayer/qgstreamerstreamscontrol.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERSTREAMSCONTROL_H +#define QGSTREAMERSTREAMSCONTROL_H + +#include <qmediastreamscontrol.h> + +QT_USE_NAMESPACE + +class QGstreamerPlayerSession; + +class QGstreamerStreamsControl : public QMediaStreamsControl +{ + Q_OBJECT +public: + QGstreamerStreamsControl(QGstreamerPlayerSession *session, QObject *parent); + virtual ~QGstreamerStreamsControl(); + + virtual int streamCount(); + virtual StreamType streamType(int streamNumber); + + virtual QVariant metaData(int streamNumber, QtMultimediaKit::MetaData key); + + virtual bool isActive(int streamNumber); + virtual void setActive(int streamNumber, bool state); + +private: + QGstreamerPlayerSession *m_session; +}; + +#endif // QGSTREAMERSTREAMSCONTROL_H + diff --git a/src/plugins/gstreamer/qabstractgstbufferpool.h b/src/plugins/gstreamer/qabstractgstbufferpool.h new file mode 100644 index 000000000..8681dac71 --- /dev/null +++ b/src/plugins/gstreamer/qabstractgstbufferpool.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTBUFFERPOOL_H +#define QGSTBUFFERPOOL_H + +#include <qabstractvideobuffer.h> +#include <qvideosurfaceformat.h> + +#include <gst/gst.h> + +/*! + Abstract interface for video buffers allocation. +*/ +class QAbstractGstBufferPool +{ +public: + virtual ~QAbstractGstBufferPool() {} + + virtual bool isFormatSupported(const QVideoSurfaceFormat &format) const = 0; + + virtual GType bufferType() const = 0; + virtual GstBuffer *takeBuffer(const QVideoSurfaceFormat &format, GstCaps *caps) = 0; + virtual void clear() = 0; + + virtual QAbstractVideoBuffer::HandleType handleType() const = 0; + + /*! + Build an QAbstractVideoBuffer instance from compatible (mathcing gst buffer type) + GstBuffer. + + This method is called from gstreamer video sink thread. + */ + virtual QAbstractVideoBuffer *prepareVideoBuffer(GstBuffer *buffer, int bytesPerLine) = 0; +}; + +#endif diff --git a/src/plugins/gstreamer/qgstreameraudioinputendpointselector.cpp b/src/plugins/gstreamer/qgstreameraudioinputendpointselector.cpp new file mode 100644 index 000000000..606f06052 --- /dev/null +++ b/src/plugins/gstreamer/qgstreameraudioinputendpointselector.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreameraudioinputendpointselector.h" + +#include <QtGui/QIcon> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include <gst/gst.h> + +#ifdef HAVE_ALSA +#include <alsa/asoundlib.h> +#endif + +QGstreamerAudioInputEndpointSelector::QGstreamerAudioInputEndpointSelector(QObject *parent) + :QAudioEndpointSelector(parent) +{ + update(); +} + +QGstreamerAudioInputEndpointSelector::~QGstreamerAudioInputEndpointSelector() +{ +} + +QList<QString> QGstreamerAudioInputEndpointSelector::availableEndpoints() const +{ + return m_names; +} + +QString QGstreamerAudioInputEndpointSelector::endpointDescription(const QString& name) const +{ + QString desc; + + for (int i = 0; i < m_names.size(); i++) { + if (m_names.at(i).compare(name) == 0) { + desc = m_descriptions.at(i); + break; + } + } + return desc; +} + +QString QGstreamerAudioInputEndpointSelector::defaultEndpoint() const +{ + if (m_names.size() > 0) + return m_names.at(0); + + return QString(); +} + +QString QGstreamerAudioInputEndpointSelector::activeEndpoint() const +{ + return m_audioInput; +} + +void QGstreamerAudioInputEndpointSelector::setActiveEndpoint(const QString& name) +{ + if (m_audioInput.compare(name) != 0) { + m_audioInput = name; + emit activeEndpointChanged(name); + } +} + +void QGstreamerAudioInputEndpointSelector::update() +{ + m_names.clear(); + m_descriptions.clear(); +#ifndef Q_WS_MAEMO_5 + updateAlsaDevices(); + updateOssDevices(); +#endif + updatePulseDevices(); + if (m_names.size() > 0) + m_audioInput = m_names.at(0); +} + +void QGstreamerAudioInputEndpointSelector::updateAlsaDevices() +{ +#ifdef HAVE_ALSA + void **hints, **n; + if (snd_device_name_hint(-1, "pcm", &hints) < 0) { + qWarning()<<"no alsa devices available"; + return; + } + n = hints; + + while (*n != NULL) { + char *name = snd_device_name_get_hint(*n, "NAME"); + char *descr = snd_device_name_get_hint(*n, "DESC"); + char *io = snd_device_name_get_hint(*n, "IOID"); + + if ((name != NULL) && (descr != NULL)) { + if ( io == NULL || qstrcmp(io,"Input") == 0 ) { + m_names.append(QLatin1String("alsa:")+QString::fromUtf8(name)); + m_descriptions.append(QString::fromUtf8(descr)); + } + } + + if (name != NULL) + free(name); + if (descr != NULL) + free(descr); + if (io != NULL) + free(io); + n++; + } + snd_device_name_free_hint(hints); +#endif +} + +void QGstreamerAudioInputEndpointSelector::updateOssDevices() +{ + QDir devDir("/dev"); + devDir.setFilter(QDir::System); +#ifndef QT_QWS_N810 + QFileInfoList entries = devDir.entryInfoList(QStringList() << "dsp*"); + foreach(const QFileInfo& entryInfo, entries) { + m_names.append(QLatin1String("oss:")+entryInfo.filePath()); + m_descriptions.append(QString("OSS device %1").arg(entryInfo.fileName())); + } +#else + m_names.append("dsppcm"); + m_descriptions.append("PCM audio input"); +#endif +} + +void QGstreamerAudioInputEndpointSelector::updatePulseDevices() +{ + GstElementFactory *factory = gst_element_factory_find("pulsesrc"); + if (factory) { + m_names.append("pulseaudio:"); + m_descriptions.append("PulseAudio device."); + gst_object_unref(GST_OBJECT(factory)); + } +} diff --git a/src/plugins/gstreamer/qgstreameraudioinputendpointselector.h b/src/plugins/gstreamer/qgstreameraudioinputendpointselector.h new file mode 100644 index 000000000..c9587fcb8 --- /dev/null +++ b/src/plugins/gstreamer/qgstreameraudioinputendpointselector.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERAUDIOINPUTENDPOINTSELECTOR_H +#define QGSTREAMERAUDIOINPUTENDPOINTSELECTOR_H + +#include <qaudioendpointselector.h> +#include <QtCore/qstringlist.h> + +QT_USE_NAMESPACE + +class QGstreamerAudioInputEndpointSelector : public QAudioEndpointSelector +{ +Q_OBJECT +public: + QGstreamerAudioInputEndpointSelector(QObject *parent); + ~QGstreamerAudioInputEndpointSelector(); + + QList<QString> availableEndpoints() const; + QString endpointDescription(const QString& name) const; + QString defaultEndpoint() const; + QString activeEndpoint() const; + +public Q_SLOTS: + void setActiveEndpoint(const QString& name); + +private: + void update(); + void updateAlsaDevices(); + void updateOssDevices(); + void updatePulseDevices(); + + QString m_audioInput; + QList<QString> m_names; + QList<QString> m_descriptions; +}; + +#endif // QGSTREAMERAUDIOINPUTENDPOINTSELECTOR_H diff --git a/src/plugins/gstreamer/qgstreamerbushelper.cpp b/src/plugins/gstreamer/qgstreamerbushelper.cpp new file mode 100644 index 000000000..a60ec7fab --- /dev/null +++ b/src/plugins/gstreamer/qgstreamerbushelper.cpp @@ -0,0 +1,203 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QMap> +#include <QTimer> +#include <QMutex> + +#include "qgstreamerbushelper.h" + + +#ifndef QT_NO_GLIB +class QGstreamerBusHelperPrivate : public QObject +{ + Q_OBJECT + +public: + void addWatch(GstBus* bus, QGstreamerBusHelper* helper) + { + setParent(helper); + m_tag = gst_bus_add_watch_full(bus, 0, busCallback, this, NULL); + m_helper = helper; + filter = 0; + } + + void removeWatch(QGstreamerBusHelper* helper) + { + Q_UNUSED(helper); + g_source_remove(m_tag); + } + + static QGstreamerBusHelperPrivate* instance() + { + return new QGstreamerBusHelperPrivate; + } + +private: + void processMessage(GstBus* bus, GstMessage* message) + { + Q_UNUSED(bus); + emit m_helper->message(message); + } + + static gboolean busCallback(GstBus *bus, GstMessage *message, gpointer data) + { + reinterpret_cast<QGstreamerBusHelperPrivate*>(data)->processMessage(bus, message); + return TRUE; + } + + guint m_tag; + QGstreamerBusHelper* m_helper; + +public: + GstBus* bus; + QGstreamerSyncEventFilter *filter; + QMutex filterMutex; +}; + +#else + +class QGstreamerBusHelperPrivate : public QObject +{ + Q_OBJECT + typedef QMap<QGstreamerBusHelper*, GstBus*> HelperMap; + +public: + void addWatch(GstBus* bus, QGstreamerBusHelper* helper) + { + m_helperMap.insert(helper, bus); + + if (m_helperMap.size() == 1) + m_intervalTimer->start(); + } + + void removeWatch(QGstreamerBusHelper* helper) + { + m_helperMap.remove(helper); + + if (m_helperMap.size() == 0) + m_intervalTimer->stop(); + } + + static QGstreamerBusHelperPrivate* instance() + { + static QGstreamerBusHelperPrivate self; + + return &self; + } + +private slots: + void interval() + { + for (HelperMap::iterator it = m_helperMap.begin(); it != m_helperMap.end(); ++it) { + GstMessage* message; + + while ((message = gst_bus_poll(it.value(), GST_MESSAGE_ANY, 0)) != 0) { + emit it.key()->message(message); + gst_message_unref(message); + } + + emit it.key()->message(QGstreamerMessage()); + } + } + +private: + QGstreamerBusHelperPrivate() + { + m_intervalTimer = new QTimer(this); + m_intervalTimer->setInterval(250); + + connect(m_intervalTimer, SIGNAL(timeout()), SLOT(interval())); + } + + HelperMap m_helperMap; + QTimer* m_intervalTimer; + +public: + GstBus* bus; + QGstreamerSyncEventFilter *filter; + QMutex filterMutex; +}; +#endif + + +static GstBusSyncReply syncGstBusFilter(GstBus* bus, GstMessage* message, QGstreamerBusHelperPrivate *d) +{ + Q_UNUSED(bus); + QMutexLocker lock(&d->filterMutex); + + bool res = false; + + if (d->filter) + res = d->filter->processSyncMessage(QGstreamerMessage(message)); + + return res ? GST_BUS_DROP : GST_BUS_PASS; +} + + +/*! + \class gstreamer::QGstreamerBusHelper + \internal +*/ + +QGstreamerBusHelper::QGstreamerBusHelper(GstBus* bus, QObject* parent): + QObject(parent), + d(QGstreamerBusHelperPrivate::instance()) +{ + d->bus = bus; + d->addWatch(bus, this); + + gst_bus_set_sync_handler(bus, (GstBusSyncHandler)syncGstBusFilter, d); +} + +QGstreamerBusHelper::~QGstreamerBusHelper() +{ + d->removeWatch(this); + gst_bus_set_sync_handler(d->bus,0,0); +} + +void QGstreamerBusHelper::installSyncEventFilter(QGstreamerSyncEventFilter *filter) +{ + QMutexLocker lock(&d->filterMutex); + d->filter = filter; +} + +#include "qgstreamerbushelper.moc" diff --git a/src/plugins/gstreamer/qgstreamerbushelper.h b/src/plugins/gstreamer/qgstreamerbushelper.h new file mode 100644 index 000000000..5375c44ae --- /dev/null +++ b/src/plugins/gstreamer/qgstreamerbushelper.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERBUSHELPER_H +#define QGSTREAMERBUSHELPER_H + +#include <QObject> + +#include <qgstreamermessage.h> +#include <gst/gst.h> + +class QGstreamerSyncEventFilter { +public: + //returns true if message was processed and should be dropped, false otherwise + virtual bool processSyncMessage(const QGstreamerMessage &message) = 0; +}; + +class QGstreamerBusHelperPrivate; + +class QGstreamerBusHelper : public QObject +{ + Q_OBJECT + friend class QGstreamerBusHelperPrivate; + +public: + QGstreamerBusHelper(GstBus* bus, QObject* parent = 0); + ~QGstreamerBusHelper(); + + void installSyncEventFilter(QGstreamerSyncEventFilter *filter); + +signals: + void message(QGstreamerMessage const& message); + + +private: + QGstreamerBusHelperPrivate* d; +}; + +#endif diff --git a/src/plugins/gstreamer/qgstreamergltexturerenderer.cpp b/src/plugins/gstreamer/qgstreamergltexturerenderer.cpp new file mode 100644 index 000000000..5d1a11ffb --- /dev/null +++ b/src/plugins/gstreamer/qgstreamergltexturerenderer.cpp @@ -0,0 +1,578 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qvideosurfacegstsink.h" +#include "qabstractvideosurface.h" +#include "qgstutils.h" + +#include <QtGui/qevent.h> +#include <QtGui/qapplication.h> +#include <QtGui/qx11info_x11.h> +#include <QtCore/qdebug.h> +#include <QtCore/qthread.h> + +#include <QtOpenGL/qgl.h> + +#include <gst/gst.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/propertyprobe.h> +#include <gst/interfaces/meegovideotexture.h> +#include <gst/interfaces/meegovideorenderswitch.h> + + +#include <EGL/egl.h> +#include <EGL/eglext.h> + +#include "qgstreamergltexturerenderer.h" + +//#define GL_TEXTURE_SINK_DEBUG 1 + +//from extdefs.h +typedef void *EGLSyncKHR; +typedef khronos_utime_nanoseconds_t EGLTimeKHR; + +#define GL_TEXTURE_EXTERNAL_OES 0x8D65 +#define EGL_SYNC_FENCE_KHR 0x30F9 + +typedef EGLSyncKHR (EGLAPIENTRYP _PFNEGLCREATESYNCKHRPROC) (EGLDisplay dpy, + EGLenum type, const EGLint * attrib_list); +typedef EGLBoolean (EGLAPIENTRYP _PFNEGLDESTROYSYNCKHRPROC) (EGLDisplay dpy, + EGLSyncKHR sync); + + +const QAbstractVideoBuffer::HandleType EGLImageTextureHandle = + QAbstractVideoBuffer::HandleType(QAbstractVideoBuffer::UserHandle+3434); + +// EGLSync functions +_PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR; +_PFNEGLDESTROYSYNCKHRPROC eglDestroySyncKHR; + +class QGStreamerGLTextureBuffer : public QAbstractVideoBuffer +{ +public: + QGStreamerGLTextureBuffer(MeegoGstVideoTexture *textureSink, int frameNumber) : + QAbstractVideoBuffer(EGLImageTextureHandle), + m_textureSink(MEEGO_GST_VIDEO_TEXTURE(textureSink)), + m_frameNumber(frameNumber) + { + } + + ~QGStreamerGLTextureBuffer() + { + } + + + MapMode mapMode() const { return NotMapped; } + uchar *map(MapMode mode, int *numBytes, int *bytesPerLine) + { + Q_UNUSED(mode); + Q_UNUSED(numBytes); + Q_UNUSED(bytesPerLine); + + //acquire_frame should really be called at buffer construction time + //but it conflicts with id-less implementation of gst texture sink. +#if defined(GL_TEXTURE_SINK_DEBUG) && GL_TEXTURE_SINK_DEBUG > 1 + qDebug() << "acquire frame" << m_frameNumber; +#endif + if (!meego_gst_video_texture_acquire_frame(m_textureSink,m_frameNumber)) + qWarning() << Q_FUNC_INFO << "acquire-frame failed" << m_frameNumber; + + +#if defined(GL_TEXTURE_SINK_DEBUG) && GL_TEXTURE_SINK_DEBUG > 1 + qDebug() << "map frame" << m_frameNumber; +#endif + + gboolean bind_status = meego_gst_video_texture_bind_frame(m_textureSink, GL_TEXTURE_EXTERNAL_OES, m_frameNumber); + if (!bind_status) + qWarning() << Q_FUNC_INFO << "bind-frame failed"; + + return (uchar*)1; + } + + void unmap() + { + gboolean bind_status = meego_gst_video_texture_bind_frame(m_textureSink, GL_TEXTURE_EXTERNAL_OES, -1); + +#if defined(GL_TEXTURE_SINK_DEBUG) && GL_TEXTURE_SINK_DEBUG > 1 + qDebug() << "unmap frame" << m_frameNumber; +#endif + + if (!bind_status) + qWarning() << Q_FUNC_INFO << "unbind-frame failed"; + + //release_frame should really be called in destructor + //but this conflicts with id-less implementation of gst texture sink. +#if defined(GL_TEXTURE_SINK_DEBUG) && GL_TEXTURE_SINK_DEBUG > 1 + qDebug() << "release frame" << m_frameNumber; +#endif + EGLSyncKHR sync = eglCreateSyncKHR(eglGetDisplay((EGLNativeDisplayType)QX11Info::display()), EGL_SYNC_FENCE_KHR, NULL); + meego_gst_video_texture_release_frame(m_textureSink, m_frameNumber, sync); + } + + QVariant handle() const + { + return m_frameNumber; + } + +private: + MeegoGstVideoTexture *m_textureSink; + int m_frameNumber; +}; + + +QGstreamerGLTextureRenderer::QGstreamerGLTextureRenderer(QObject *parent) : + QVideoRendererControl(parent), + m_videoSink(0), + m_surface(0), + m_context(0), + m_winId(0), + m_colorKey(49,0,49), + m_overlayEnabled(false), + m_bufferProbeId(-1) +{ + eglCreateSyncKHR = + (_PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR"); + eglDestroySyncKHR = + (_PFNEGLDESTROYSYNCKHRPROC)eglGetProcAddress("eglDestroySyncKHR"); +} + +QGstreamerGLTextureRenderer::~QGstreamerGLTextureRenderer() +{ + if (m_surface && m_surface->isActive()) + m_surface->stop(); + + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); +} + +GstElement *QGstreamerGLTextureRenderer::videoSink() +{ + if (!m_videoSink && isReady()) { + if (m_context && !m_surface->supportedPixelFormats(EGLImageTextureHandle).isEmpty()) { +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << ": using gltexture sink"; +#endif + if (m_context) + m_context->makeCurrent(); + m_videoSink = gst_element_factory_make("gltexturesink", "egl-texture-sink"); + g_object_set(G_OBJECT(m_videoSink), + "x-display", QX11Info::display(), + "egl-display", eglGetDisplay((EGLNativeDisplayType)QX11Info::display()), + "egl-context", eglGetCurrentContext(), + "colorkey", m_colorKey.rgb(), + "autopaint-colorkey", false, + "use-framebuffer-memory", true, + "render-mode", m_overlayEnabled ? VIDEO_RENDERSWITCH_XOVERLAY_MODE + : VIDEO_RENDERSWITCH_TEXTURE_STREAMING_MODE, + (char*)NULL); + + g_signal_connect(G_OBJECT(m_videoSink), "frame-ready", G_CALLBACK(handleFrameReady), (gpointer)this); + } else { + qWarning() << Q_FUNC_INFO << ": Fallback to QVideoSurfaceGstSink since EGLImageTextureHandle is not supported"; + m_videoSink = reinterpret_cast<GstElement*>(QVideoSurfaceGstSink::createSink(m_surface)); + } + + if (m_videoSink) { + gst_object_ref(GST_OBJECT(m_videoSink)); //Take ownership + gst_object_sink(GST_OBJECT(m_videoSink)); + + GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink"); + m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padBufferProbe), this); + } + } + + return m_videoSink; +} + +QAbstractVideoSurface *QGstreamerGLTextureRenderer::surface() const +{ + return m_surface; +} + +void QGstreamerGLTextureRenderer::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface != surface) { +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << surface; +#endif + + bool oldReady = isReady(); + + 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, SIGNAL(supportedFormatsChanged()), + this, SLOT(handleFormatChange())); + } + + m_surface = surface; + + if (oldReady != isReady()) + emit readyChanged(!oldReady); + + if (m_surface) { + connect(m_surface, SIGNAL(supportedFormatsChanged()), + this, SLOT(handleFormatChange())); + } + + emit sinkChanged(); + } +} + +void QGstreamerGLTextureRenderer::handleFormatChange() +{ + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); + + m_videoSink = 0; + emit sinkChanged(); +} + +void QGstreamerGLTextureRenderer::handleFrameReady(GstElement *sink, gint frame, gpointer data) +{ + Q_UNUSED(sink); + QGstreamerGLTextureRenderer* renderer = reinterpret_cast<QGstreamerGLTextureRenderer*>(data); + + QMutexLocker locker(&renderer->m_mutex); + QMetaObject::invokeMethod(renderer, "renderGLFrame", + Qt::QueuedConnection, + Q_ARG(int, frame)); + + //we have to wait to ensure the frame is not reused, + //timeout is added to avoid deadlocks when the main thread is + //waiting for rendering to complete, this is possible for example during state chages. + //If frame is not rendered during 60ms (~1-2 frames interval) it's better to unblock and drop it if necessary + renderer->m_renderCondition.wait(&renderer->m_mutex, 60); +} + +void QGstreamerGLTextureRenderer::renderGLFrame(int frame) +{ +#if defined(GL_TEXTURE_SINK_DEBUG) && GL_TEXTURE_SINK_DEBUG > 1 + qDebug() << Q_FUNC_INFO << "frame:" << frame << "surface active:" << m_surface->isActive(); +#endif + QMutexLocker locker(&m_mutex); + + if (!m_surface) { + m_renderCondition.wakeAll(); + return; + } + + MeegoGstVideoTexture *textureSink = MEEGO_GST_VIDEO_TEXTURE(m_videoSink); + + if (m_context) + m_context->makeCurrent(); + + //don't try to render the frame if state is changed to NULL or READY + GstState pendingState = GST_STATE_NULL; + GstState newState = GST_STATE_NULL; + GstStateChangeReturn res = gst_element_get_state(m_videoSink, + &newState, + &pendingState, + 0);//don't block and return immediately + + if (res == GST_STATE_CHANGE_FAILURE || + newState == GST_STATE_NULL || + pendingState == GST_STATE_NULL) { + stopRenderer(); + m_renderCondition.wakeAll(); + return; + } + + if (!m_surface->isActive()) { + //find the native video size + GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink"); + GstCaps *caps = gst_pad_get_negotiated_caps(pad); + + if (caps) { + 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, EGLImageTextureHandle); + if (!m_surface->start(format)) { + qWarning() << Q_FUNC_INFO << "failed to start video surface" << format; + m_renderCondition.wakeAll(); + return; + } + } + + QGStreamerGLTextureBuffer *buffer = new QGStreamerGLTextureBuffer(textureSink, frame); + QVideoFrame videoFrame(buffer, + m_surface->surfaceFormat().frameSize(), + m_surface->surfaceFormat().pixelFormat()); + m_surface->present(videoFrame); + m_renderCondition.wakeAll(); +} + +bool QGstreamerGLTextureRenderer::isReady() const +{ + if (!m_surface) + return false; + + if (m_winId > 0) + return true; + + //winId is required only for EGLImageTextureHandle compatible surfaces + return m_surface->supportedPixelFormats(EGLImageTextureHandle).isEmpty(); +} + +void QGstreamerGLTextureRenderer::handleBusMessage(GstMessage* gm) +{ +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << GST_MESSAGE_TYPE_NAME(gm); +#endif + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_STATE_CHANGED) { + GstState oldState; + GstState newState; + gst_message_parse_state_changed(gm, &oldState, &newState, 0); + +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << "State changed:" << oldState << newState; +#endif + + if (newState == GST_STATE_READY || newState == GST_STATE_NULL) { + stopRenderer(); + } + + if (oldState == GST_STATE_READY && newState == GST_STATE_PAUSED) { + updateNativeVideoSize(); + } + } +} + +void QGstreamerGLTextureRenderer::handleSyncMessage(GstMessage* gm) +{ +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO; +#endif + + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(gm->structure, "prepare-xwindow-id")) + precessNewStream(); +} + +void QGstreamerGLTextureRenderer::precessNewStream() +{ + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + GstXOverlay *overlay = GST_X_OVERLAY(m_videoSink); + + gst_x_overlay_set_xwindow_id(overlay, m_winId); + + if (!m_displayRect.isEmpty()) { + gst_x_overlay_set_render_rectangle(overlay, + m_displayRect.x(), + m_displayRect.y(), + m_displayRect.width(), + m_displayRect.height()); + } + + GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink"); + m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padBufferProbe), this); + } +} + +void QGstreamerGLTextureRenderer::stopRenderer() +{ +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO; +#endif + + if (m_surface && m_surface->isActive()) + m_surface->stop(); + + if (!m_nativeSize.isEmpty()) { + m_nativeSize = QSize(); + emit nativeSizeChanged(); + } +} + +bool QGstreamerGLTextureRenderer::overlayEnabled() const +{ + return m_overlayEnabled; +} + +void QGstreamerGLTextureRenderer::setOverlayEnabled(bool enabled) +{ + + if (m_videoSink && (m_overlayEnabled != enabled)) { + qDebug() << Q_FUNC_INFO << enabled; + g_object_set(G_OBJECT(m_videoSink), + "render-mode", + enabled ? VIDEO_RENDERSWITCH_XOVERLAY_MODE : VIDEO_RENDERSWITCH_TEXTURE_STREAMING_MODE, + (char *)NULL); + } + + m_overlayEnabled = enabled; +} + + +WId QGstreamerGLTextureRenderer::winId() const +{ + return m_winId; +} + +void QGstreamerGLTextureRenderer::setWinId(WId id) +{ +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << id; +#endif + + if (m_winId == id) + return; + + bool oldReady = isReady(); + + m_winId = id; + + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + //don't set winId in NULL state, + //texture sink opens xvideo port on set_xwindow_id, + //this fails if video resource is not granted by resource policy yet. + //state is changed to READY/PAUSED/PLAYING only after resource is granted. + GstState pendingState = GST_STATE_NULL; + GstState newState = GST_STATE_NULL; + GstStateChangeReturn res = gst_element_get_state(m_videoSink, + &newState, + &pendingState, + 0);//don't block and return immediately + + if (res != GST_STATE_CHANGE_FAILURE && + newState != GST_STATE_NULL && + pendingState != GST_STATE_NULL) + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_winId); + } + + if (oldReady != isReady()) + emit readyChanged(!oldReady); +} + +QRect QGstreamerGLTextureRenderer::overlayGeometry() const +{ + return m_displayRect; +} + +void QGstreamerGLTextureRenderer::setOverlayGeometry(const QRect &geometry) +{ + if (m_displayRect != geometry) { +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << geometry; +#endif + m_displayRect = geometry; + + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + if (m_displayRect.isEmpty()) + gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), -1, -1, -1, -1); + else + gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), + m_displayRect.x(), + m_displayRect.y(), + m_displayRect.width(), + m_displayRect.height()); + repaintOverlay(); + } + } +} + +QColor QGstreamerGLTextureRenderer::colorKey() const +{ + return m_colorKey; +} + +void QGstreamerGLTextureRenderer::repaintOverlay() +{ + 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; + GstStateChangeReturn res = gst_element_get_state(m_videoSink, &state, NULL, 1000000); + if (res != GST_STATE_CHANGE_FAILURE && state != GST_STATE_NULL) { + gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink)); + } + } +} + +QSize QGstreamerGLTextureRenderer::nativeSize() const +{ + return m_nativeSize; +} + +gboolean QGstreamerGLTextureRenderer::padBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data) +{ + QGstreamerGLTextureRenderer *control = reinterpret_cast<QGstreamerGLTextureRenderer*>(user_data); + QMetaObject::invokeMethod(control, "updateNativeVideoSize", Qt::QueuedConnection); + gst_pad_remove_buffer_probe(pad, control->m_bufferProbeId); + + return TRUE; +} + +void QGstreamerGLTextureRenderer::updateNativeVideoSize() +{ + 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_negotiated_caps(pad); + + if (caps) { + m_nativeSize = QGstUtils::capsCorrectedResolution(caps); + gst_caps_unref(caps); + } + } else { + m_nativeSize = QSize(); + } +#ifdef GL_TEXTURE_SINK_DEBUG + qDebug() << Q_FUNC_INFO << oldSize << m_nativeSize << m_videoSink; +#endif + + if (m_nativeSize != oldSize) + emit nativeSizeChanged(); +} diff --git a/src/plugins/gstreamer/qgstreamergltexturerenderer.h b/src/plugins/gstreamer/qgstreamergltexturerenderer.h new file mode 100644 index 000000000..92beb1a17 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamergltexturerenderer.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERGLTEXTURERENDERER_H +#define QGSTREAMERGLTEXTURERENDERER_H + +#include <qvideorenderercontrol.h> +#include "qvideosurfacegstsink.h" + +#include "qgstreamervideorendererinterface.h" +#include <QtGui/qcolor.h> + +#include <X11/extensions/Xv.h> + +QT_USE_NAMESPACE + +class QGLContext; + +class QGstreamerGLTextureRenderer : public QVideoRendererControl, public QGstreamerVideoRendererInterface +{ + Q_OBJECT + Q_INTERFACES(QGstreamerVideoRendererInterface) + + Q_PROPERTY(bool overlayEnabled READ overlayEnabled WRITE setOverlayEnabled) + Q_PROPERTY(qulonglong winId READ winId WRITE setWinId) + Q_PROPERTY(QRect overlayGeometry READ overlayGeometry WRITE setOverlayGeometry) + Q_PROPERTY(QColor colorKey READ colorKey) + Q_PROPERTY(QSize nativeSize READ nativeSize NOTIFY nativeSizeChanged) + +public: + QGstreamerGLTextureRenderer(QObject *parent = 0); + virtual ~QGstreamerGLTextureRenderer(); + + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + GstElement *videoSink(); + + bool isReady() const; + void handleBusMessage(GstMessage* gm); + void handleSyncMessage(GstMessage* gm); + void precessNewStream(); + void stopRenderer(); + + int framebufferNumber() const; + + bool overlayEnabled() const; + WId winId() const; + QRect overlayGeometry() const; + QColor colorKey() const; + QSize nativeSize() const; + +public slots: + void renderGLFrame(int); + + void setOverlayEnabled(bool); + void setWinId(WId id); + void setOverlayGeometry(const QRect &geometry); + void repaintOverlay(); + +signals: + void sinkChanged(); + void readyChanged(bool); + void nativeSizeChanged(); + +private slots: + void handleFormatChange(); + void updateNativeVideoSize(); + +private: + static void handleFrameReady(GstElement *sink, gint frame, gpointer data); + static gboolean padBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data); + + GstElement *m_videoSink; + QAbstractVideoSurface *m_surface; + QGLContext *m_context; + QSize m_nativeSize; + + WId m_winId; + QColor m_colorKey; + QRect m_displayRect; + bool m_overlayEnabled; + int m_bufferProbeId; + + QMutex m_mutex; + QWaitCondition m_renderCondition; +}; + +#endif // QGSTREAMERVIDEORENDRER_H diff --git a/src/plugins/gstreamer/qgstreamermessage.cpp b/src/plugins/gstreamer/qgstreamermessage.cpp new file mode 100644 index 000000000..3b8057e09 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamermessage.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <gst/gst.h> + +#include "qgstreamermessage.h" + + +static int wuchi = qRegisterMetaType<QGstreamerMessage>(); + + +/*! + \class gstreamer::QGstreamerMessage + \internal +*/ + +QGstreamerMessage::QGstreamerMessage(): + m_message(0) +{ +} + +QGstreamerMessage::QGstreamerMessage(GstMessage* message): + m_message(message) +{ + gst_message_ref(m_message); +} + +QGstreamerMessage::QGstreamerMessage(QGstreamerMessage const& m): + m_message(m.m_message) +{ + gst_message_ref(m_message); +} + + +QGstreamerMessage::~QGstreamerMessage() +{ + if (m_message != 0) + gst_message_unref(m_message); +} + +GstMessage* QGstreamerMessage::rawMessage() const +{ + return m_message; +} + +QGstreamerMessage& QGstreamerMessage::operator=(QGstreamerMessage const& rhs) +{ + if (m_message != 0) + gst_message_unref(m_message); + + if ((m_message = rhs.m_message) != 0) + gst_message_ref(m_message); + + return *this; +} diff --git a/src/plugins/gstreamer/qgstreamermessage.h b/src/plugins/gstreamer/qgstreamermessage.h new file mode 100644 index 000000000..2bdc2a9c8 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamermessage.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERMESSAGE_H +#define QGSTREAMERMESSAGE_H + +#include <QMetaType> + +#include <gst/gst.h> + + +class QGstreamerMessage +{ +public: + QGstreamerMessage(); + QGstreamerMessage(GstMessage* message); + QGstreamerMessage(QGstreamerMessage const& m); + ~QGstreamerMessage(); + + GstMessage* rawMessage() const; + + QGstreamerMessage& operator=(QGstreamerMessage const& rhs); + +private: + GstMessage* m_message; +}; + +Q_DECLARE_METATYPE(QGstreamerMessage); + +#endif diff --git a/src/plugins/gstreamer/qgstreamerserviceplugin.cpp b/src/plugins/gstreamer/qgstreamerserviceplugin.cpp new file mode 100644 index 000000000..e74085664 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamerserviceplugin.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qstring.h> +#include <QtCore/qdebug.h> +#include <QtGui/QIcon> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include "qgstreamerserviceplugin.h" + +//#define QT_SUPPORTEDMIMETYPES_DEBUG + +#ifdef QMEDIA_GSTREAMER_PLAYER +#include "qgstreamerplayerservice.h" +#endif + +#if defined(QMEDIA_GSTREAMER_CAPTURE) +#include "qgstreamercaptureservice.h" +#endif + +#ifdef QMEDIA_GSTREAMER_CAMERABIN +#include "camerabinservice.h" +#endif + +#include <qmediaserviceprovider.h> + +#include <linux/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <linux/videodev2.h> + + +QStringList QGstreamerServicePlugin::keys() const +{ + return QStringList() +#ifdef QMEDIA_GSTREAMER_PLAYER + << QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER) +#endif + +#ifdef QMEDIA_GSTREAMER_CAPTURE + << QLatin1String(Q_MEDIASERVICE_AUDIOSOURCE) + << QLatin1String(Q_MEDIASERVICE_CAMERA) +#elif defined(QMEDIA_GSTREAMER_CAMERABIN) + << QLatin1String(Q_MEDIASERVICE_CAMERA) +#endif + ; + +} + +QMediaService* QGstreamerServicePlugin::create(const QString &key) +{ + static bool initialized = false; + if (!initialized) { + initialized = true; + gst_init(NULL, NULL); + } + +#ifdef QMEDIA_GSTREAMER_PLAYER + if (key == QLatin1String(Q_MEDIASERVICE_MEDIAPLAYER)) + return new QGstreamerPlayerService; +#endif + +#ifdef QMEDIA_GSTREAMER_CAMERABIN + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA) && CameraBinService::isCameraBinAvailable()) + return new CameraBinService(key); +#endif + +#ifdef QMEDIA_GSTREAMER_CAPTURE + if (key == QLatin1String(Q_MEDIASERVICE_AUDIOSOURCE)) + return new QGstreamerCaptureService(key); + + if (key == QLatin1String(Q_MEDIASERVICE_CAMERA)) + return new QGstreamerCaptureService(key); +#endif + + qWarning() << "Gstreamer service plugin: unsupported key:" << key; + return 0; +} + +void QGstreamerServicePlugin::release(QMediaService *service) +{ + delete service; +} + +QMediaServiceProviderHint::Features QGstreamerServicePlugin::supportedFeatures( + const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_MEDIAPLAYER) + return QMediaServiceProviderHint::StreamPlayback | QMediaServiceProviderHint::VideoSurface; + else if (service == Q_MEDIASERVICE_CAMERA) + return QMediaServiceProviderHint::VideoSurface; + else + return QMediaServiceProviderHint::Features(); +} + +QList<QByteArray> QGstreamerServicePlugin::devices(const QByteArray &service) const +{ + if (service == Q_MEDIASERVICE_CAMERA) { + if (m_cameraDevices.isEmpty()) + updateDevices(); + + return m_cameraDevices; + } + + return QList<QByteArray>(); +} + +QString QGstreamerServicePlugin::deviceDescription(const QByteArray &service, const QByteArray &device) +{ + if (service == Q_MEDIASERVICE_CAMERA) { + if (m_cameraDevices.isEmpty()) + updateDevices(); + + for (int i=0; i<m_cameraDevices.count(); i++) + if (m_cameraDevices[i] == device) + return m_cameraDescriptions[i]; + } + + return QString(); +} + +QVariant QGstreamerServicePlugin::deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property) +{ + Q_UNUSED(service); + Q_UNUSED(device); + Q_UNUSED(property); + return QVariant(); +} + +void QGstreamerServicePlugin::updateDevices() const +{ + m_cameraDevices.clear(); + m_cameraDescriptions.clear(); + +#ifdef Q_WS_MAEMO_5 + m_cameraDevices << "/dev/video0" << "/dev/video1"; + m_cameraDescriptions << tr("Main Camera") << tr("Front Camera"); + return; +#endif + +#ifdef Q_WS_MAEMO_6 + m_cameraDevices << "primary" << "secondary"; + m_cameraDescriptions << tr("Main camera") << tr("Front camera"); + return; +#endif + + QDir devDir("/dev"); + devDir.setFilter(QDir::System); + + QFileInfoList entries = devDir.entryInfoList(QStringList() << "video*"); + + foreach( const QFileInfo &entryInfo, entries ) { + //qDebug() << "Try" << entryInfo.filePath(); + + int fd = ::open(entryInfo.filePath().toLatin1().constData(), O_RDWR ); + if (fd == -1) + continue; + + bool isCamera = false; + + v4l2_input input; + memset(&input, 0, sizeof(input)); + for (; ::ioctl(fd, VIDIOC_ENUMINPUT, &input) >= 0; ++input.index) { + if(input.type == V4L2_INPUT_TYPE_CAMERA || input.type == 0) { + isCamera = ::ioctl(fd, VIDIOC_S_INPUT, input.index) != 0; + break; + } + } + + if (isCamera) { + // find out its driver "name" + QString name; + struct v4l2_capability vcap; + memset(&vcap, 0, sizeof(struct v4l2_capability)); + + if (ioctl(fd, VIDIOC_QUERYCAP, &vcap) != 0) + name = entryInfo.fileName(); + else + name = QString((const char*)vcap.card); + //qDebug() << "found camera: " << name; + + m_cameraDevices.append(entryInfo.filePath().toLocal8Bit()); + m_cameraDescriptions.append(name); + } + ::close(fd); + } +} + +namespace { + const char* getCodecAlias(const QString &codec) + { + if (codec.startsWith("avc1.")) + return "video/x-h264"; + + if (codec.startsWith("mp4a.")) + return "audio/mpeg4"; + + if (codec.startsWith("mp4v.20.")) + return "video/mpeg4"; + + if (codec == "samr") + return "audio/amr"; + + return 0; + } + + const char* getMimeTypeAlias(const QString &mimeType) + { + if (mimeType == "video/mp4") + return "video/mpeg4"; + + if (mimeType == "audio/mp4") + return "audio/mpeg4"; + + if (mimeType == "video/ogg" + || mimeType == "audio/ogg") + return "application/ogg"; + + return 0; + } +} + +QtMultimediaKit::SupportEstimate QGstreamerServicePlugin::hasSupport(const QString &mimeType, + const QStringList& codecs) const +{ + if (m_supportedMimeTypeSet.isEmpty()) + updateSupportedMimeTypes(); + + QString mimeTypeLowcase = mimeType.toLower(); + bool containsMimeType = m_supportedMimeTypeSet.contains(mimeTypeLowcase); + if (!containsMimeType) { + const char* mimeTypeAlias = getMimeTypeAlias(mimeTypeLowcase); + containsMimeType = m_supportedMimeTypeSet.contains(mimeTypeAlias); + if (!containsMimeType) { + containsMimeType = m_supportedMimeTypeSet.contains("video/" + mimeTypeLowcase) + || m_supportedMimeTypeSet.contains("video/x-" + mimeTypeLowcase) + || m_supportedMimeTypeSet.contains("audio/" + mimeTypeLowcase) + || m_supportedMimeTypeSet.contains("audio/x-" + mimeTypeLowcase); + } + } + + int supportedCodecCount = 0; + foreach(const QString &codec, codecs) { + QString codecLowcase = codec.toLower(); + const char* codecAlias = getCodecAlias(codecLowcase); + if (codecAlias) { + if (m_supportedMimeTypeSet.contains(codecAlias)) + supportedCodecCount++; + } else if (m_supportedMimeTypeSet.contains("video/" + codecLowcase) + || m_supportedMimeTypeSet.contains("video/x-" + codecLowcase) + || m_supportedMimeTypeSet.contains("audio/" + codecLowcase) + || m_supportedMimeTypeSet.contains("audio/x-" + codecLowcase)) { + supportedCodecCount++; + } + } + if (supportedCodecCount > 0 && supportedCodecCount == codecs.size()) + return QtMultimediaKit::ProbablySupported; + + if (supportedCodecCount == 0 && !containsMimeType) + return QtMultimediaKit::NotSupported; + + return QtMultimediaKit::MaybeSupported; +} + +void QGstreamerServicePlugin::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 +} + +QStringList QGstreamerServicePlugin::supportedMimeTypes() const +{ + return QStringList(); +} + +Q_EXPORT_PLUGIN2(qtmedia_gstengine, QGstreamerServicePlugin); diff --git a/src/plugins/gstreamer/qgstreamerserviceplugin.h b/src/plugins/gstreamer/qgstreamerserviceplugin.h new file mode 100644 index 000000000..0ce0bbd84 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamerserviceplugin.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QGSTREAMERSERVICEPLUGIN_H +#define QGSTREAMERSERVICEPLUGIN_H + +#include <qmediaserviceproviderplugin.h> +#include <QtCore/qset.h> + +QT_USE_NAMESPACE + + +class QGstreamerServicePlugin + : public QMediaServiceProviderPlugin + , public QMediaServiceSupportedDevicesInterface + , public QMediaServiceFeaturesInterface + , public QMediaServiceSupportedFormatsInterface +{ + Q_OBJECT + Q_INTERFACES(QMediaServiceSupportedDevicesInterface) + Q_INTERFACES(QMediaServiceFeaturesInterface) + Q_INTERFACES(QMediaServiceSupportedFormatsInterface) +public: + QStringList keys() const; + QMediaService* create(QString const& key); + void release(QMediaService *service); + + QMediaServiceProviderHint::Features supportedFeatures(const QByteArray &service) const; + + QList<QByteArray> devices(const QByteArray &service) const; + QString deviceDescription(const QByteArray &service, const QByteArray &device); + QVariant deviceProperty(const QByteArray &service, const QByteArray &device, const QByteArray &property); + + QtMultimediaKit::SupportEstimate hasSupport(const QString &mimeType, const QStringList& codecs) const; + QStringList supportedMimeTypes() const; + +private: + void updateDevices() const; + + mutable QList<QByteArray> m_cameraDevices; + mutable QStringList m_cameraDescriptions; + mutable QSet<QString> m_supportedMimeTypeSet; //for fast access + + void updateSupportedMimeTypes() const; +}; + +#endif // QGSTREAMERSERVICEPLUGIN_H diff --git a/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.cpp b/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.cpp new file mode 100644 index 000000000..d53497d30 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.cpp @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideoinputdevicecontrol.h" + +#include <QtGui/QIcon> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include <linux/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <linux/videodev2.h> + +QGstreamerVideoInputDeviceControl::QGstreamerVideoInputDeviceControl(QObject *parent) + :QVideoDeviceControl(parent), m_selectedDevice(0) +{ + update(); +} + +QGstreamerVideoInputDeviceControl::~QGstreamerVideoInputDeviceControl() +{ +} + +int QGstreamerVideoInputDeviceControl::deviceCount() const +{ + return m_names.size(); +} + +QString QGstreamerVideoInputDeviceControl::deviceName(int index) const +{ + return m_names[index]; +} + +QString QGstreamerVideoInputDeviceControl::deviceDescription(int index) const +{ + return m_descriptions[index]; +} + +QIcon QGstreamerVideoInputDeviceControl::deviceIcon(int index) const +{ + Q_UNUSED(index); + return QIcon(); +} + +int QGstreamerVideoInputDeviceControl::defaultDevice() const +{ + return 0; +} + +int QGstreamerVideoInputDeviceControl::selectedDevice() const +{ + return m_selectedDevice; +} + + +void QGstreamerVideoInputDeviceControl::setSelectedDevice(int index) +{ + if (index != m_selectedDevice) { + m_selectedDevice = index; + emit selectedDeviceChanged(index); + emit selectedDeviceChanged(deviceName(index)); + } +} + + +void QGstreamerVideoInputDeviceControl::update() +{ + m_names.clear(); + m_descriptions.clear(); + +#ifdef Q_WS_MAEMO_6 + m_names << QLatin1String("primary") << QLatin1String("secondary"); + m_descriptions << tr("Main camera") << tr("Front camera"); +#else + QDir devDir("/dev"); + devDir.setFilter(QDir::System); + + QFileInfoList entries = devDir.entryInfoList(QStringList() << "video*"); + + foreach( const QFileInfo &entryInfo, entries ) { + //qDebug() << "Try" << entryInfo.filePath(); + + int fd = ::open(entryInfo.filePath().toLatin1().constData(), O_RDWR ); + if (fd == -1) + continue; + + bool isCamera = false; + + v4l2_input input; + memset(&input, 0, sizeof(input)); + for (; ::ioctl(fd, VIDIOC_ENUMINPUT, &input) >= 0; ++input.index) { + if(input.type == V4L2_INPUT_TYPE_CAMERA || input.type == 0) { + isCamera = ::ioctl(fd, VIDIOC_S_INPUT, input.index) != 0; + break; + } + } + + if (isCamera) { + // find out its driver "name" + QString name; + struct v4l2_capability vcap; + memset(&vcap, 0, sizeof(struct v4l2_capability)); + + if (ioctl(fd, VIDIOC_QUERYCAP, &vcap) != 0) + name = entryInfo.fileName(); + else + name = QString((const char*)vcap.card); + //qDebug() << "found camera: " << name; + + m_names.append(entryInfo.filePath()); + m_descriptions.append(name); + } + ::close(fd); + } +#endif +} diff --git a/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.h b/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.h new file mode 100644 index 000000000..a31636239 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideoinputdevicecontrol.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOINPUTDEVICECONTROL_H +#define QGSTREAMERVIDEOINPUTDEVICECONTROL_H + +#include <qvideodevicecontrol.h> +#include <QtCore/qstringlist.h> + +QT_USE_NAMESPACE + +class QGstreamerVideoInputDeviceControl : public QVideoDeviceControl +{ +Q_OBJECT +public: + QGstreamerVideoInputDeviceControl(QObject *parent); + ~QGstreamerVideoInputDeviceControl(); + + int deviceCount() const; + + QString deviceName(int index) const; + QString deviceDescription(int index) const; + QIcon deviceIcon(int index) const; + + int defaultDevice() const; + int selectedDevice() const; + +public Q_SLOTS: + void setSelectedDevice(int index); + +private: + void update(); + + int m_selectedDevice; + QStringList m_names; + QStringList m_descriptions; +}; + +#endif // QGSTREAMERAUDIOINPUTDEVICECONTROL_H diff --git a/src/plugins/gstreamer/qgstreamervideooverlay.cpp b/src/plugins/gstreamer/qgstreamervideooverlay.cpp new file mode 100644 index 000000000..3e19dd075 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideooverlay.cpp @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideooverlay.h" +#include "qvideosurfacegstsink.h" + +#include <qvideosurfaceformat.h> + +#include "qx11videosurface.h" + +#ifndef QT_NO_XVIDEO + +QGstreamerVideoOverlay::QGstreamerVideoOverlay(QObject *parent) + : QVideoWindowControl(parent) + , m_surface(new QX11VideoSurface) + , m_videoSink(reinterpret_cast<GstElement*>(QVideoSurfaceGstSink::createSink(m_surface))) + , m_aspectRatioMode(Qt::KeepAspectRatio) + , m_fullScreen(false) +{ + if (m_videoSink) { + gst_object_ref(GST_OBJECT(m_videoSink)); //Take ownership + gst_object_sink(GST_OBJECT(m_videoSink)); + } + + connect(m_surface, SIGNAL(surfaceFormatChanged(QVideoSurfaceFormat)), + this, SLOT(surfaceFormatChanged())); +} + +QGstreamerVideoOverlay::~QGstreamerVideoOverlay() +{ + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); + + delete m_surface; +} + +WId QGstreamerVideoOverlay::winId() const +{ + return m_surface->winId(); +} + +void QGstreamerVideoOverlay::setWinId(WId id) +{ + bool wasReady = isReady(); + m_surface->setWinId(id); + + if (isReady() != wasReady) + emit readyChanged(!wasReady); +} + +QRect QGstreamerVideoOverlay::displayRect() const +{ + return m_displayRect; +} + +void QGstreamerVideoOverlay::setDisplayRect(const QRect &rect) +{ + m_displayRect = rect; + + setScaledDisplayRect(); +} + +Qt::AspectRatioMode QGstreamerVideoOverlay::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void QGstreamerVideoOverlay::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + m_aspectRatioMode = mode; + + setScaledDisplayRect(); +} + +void QGstreamerVideoOverlay::repaint() +{ +} + +int QGstreamerVideoOverlay::brightness() const +{ + return m_surface->brightness(); +} + +void QGstreamerVideoOverlay::setBrightness(int brightness) +{ + m_surface->setBrightness(brightness); + + emit brightnessChanged(m_surface->brightness()); +} + +int QGstreamerVideoOverlay::contrast() const +{ + return m_surface->contrast(); +} + +void QGstreamerVideoOverlay::setContrast(int contrast) +{ + m_surface->setContrast(contrast); + + emit contrastChanged(m_surface->contrast()); +} + +int QGstreamerVideoOverlay::hue() const +{ + return m_surface->hue(); +} + +void QGstreamerVideoOverlay::setHue(int hue) +{ + m_surface->setHue(hue); + + emit hueChanged(m_surface->hue()); +} + +int QGstreamerVideoOverlay::saturation() const +{ + return m_surface->saturation(); +} + +void QGstreamerVideoOverlay::setSaturation(int saturation) +{ + m_surface->setSaturation(saturation); + + emit saturationChanged(m_surface->saturation()); +} + +bool QGstreamerVideoOverlay::isFullScreen() const +{ + return m_fullScreen; +} + +void QGstreamerVideoOverlay::setFullScreen(bool fullScreen) +{ + emit fullScreenChanged(m_fullScreen = fullScreen); +} + +QSize QGstreamerVideoOverlay::nativeSize() const +{ + return m_surface->surfaceFormat().sizeHint(); +} + +QAbstractVideoSurface *QGstreamerVideoOverlay::surface() const +{ + return m_surface; +} + +GstElement *QGstreamerVideoOverlay::videoSink() +{ + return m_videoSink; +} + +void QGstreamerVideoOverlay::surfaceFormatChanged() +{ + setScaledDisplayRect(); + + emit nativeSizeChanged(); +} + +void QGstreamerVideoOverlay::setScaledDisplayRect() +{ + QRect formatViewport = m_surface->surfaceFormat().viewport(); + + switch (m_aspectRatioMode) { + case Qt::KeepAspectRatio: + { + QSize size = m_surface->surfaceFormat().sizeHint(); + size.scale(m_displayRect.size(), Qt::KeepAspectRatio); + + QRect rect(QPoint(0, 0), size); + rect.moveCenter(m_displayRect.center()); + + m_surface->setDisplayRect(rect); + m_surface->setViewport(formatViewport); + } + break; + case Qt::IgnoreAspectRatio: + m_surface->setDisplayRect(m_displayRect); + m_surface->setViewport(formatViewport); + break; + case Qt::KeepAspectRatioByExpanding: + { + QSize size = m_displayRect.size(); + size.scale(m_surface->surfaceFormat().sizeHint(), Qt::KeepAspectRatio); + + QRect viewport(QPoint(0, 0), size); + viewport.moveCenter(formatViewport.center()); + m_surface->setDisplayRect(m_displayRect); + m_surface->setViewport(viewport); + } + break; + }; +} + +#endif //QT_NO_XVIDEO diff --git a/src/plugins/gstreamer/qgstreamervideooverlay.h b/src/plugins/gstreamer/qgstreamervideooverlay.h new file mode 100644 index 000000000..194c4ea32 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideooverlay.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOOVERLAY_H +#define QGSTREAMERVIDEOOVERLAY_H + +#include <qvideowindowcontrol.h> + +#include "qgstreamervideorendererinterface.h" + +QT_BEGIN_NAMESPACE +class QAbstractVideoSurface; +QT_END_NAMESPACE +class QX11VideoSurface; + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + +QT_USE_NAMESPACE + +class QGstreamerVideoOverlay : public QVideoWindowControl, public QGstreamerVideoRendererInterface +{ + Q_OBJECT + Q_INTERFACES(QGstreamerVideoRendererInterface) +public: + QGstreamerVideoOverlay(QObject *parent = 0); + ~QGstreamerVideoOverlay(); + + WId winId() const; + void setWinId(WId id); + + QRect displayRect() const; + void setDisplayRect(const QRect &rect); + + bool isFullScreen() const; + void setFullScreen(bool fullScreen); + + QSize nativeSize() const; + + Qt::AspectRatioMode aspectRatioMode() const; + void setAspectRatioMode(Qt::AspectRatioMode mode); + + void repaint(); + + int brightness() const; + void setBrightness(int brightness); + + int contrast() const; + void setContrast(int contrast); + + int hue() const; + void setHue(int hue); + + int saturation() const; + void setSaturation(int saturation); + + QAbstractVideoSurface *surface() const; + + GstElement *videoSink(); + + bool isReady() const { return winId() != 0; } + +signals: + void sinkChanged(); + void readyChanged(bool); + +private slots: + void surfaceFormatChanged(); + +private: + void setScaledDisplayRect(); + + QX11VideoSurface *m_surface; + GstElement *m_videoSink; + Qt::AspectRatioMode m_aspectRatioMode; + QRect m_displayRect; + bool m_fullScreen; +}; + +#endif //QT_NO_XVIDEO + +#endif diff --git a/src/plugins/gstreamer/qgstreamervideorenderer.cpp b/src/plugins/gstreamer/qgstreamervideorenderer.cpp new file mode 100644 index 000000000..6c4e6b90c --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideorenderer.cpp @@ -0,0 +1,120 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideorenderer.h" +#include "qvideosurfacegstsink.h" +#include "qabstractvideosurface.h" + +#include <QEvent> +#include <QApplication> +#include <QDebug> + +#include <gst/gst.h> + +QGstreamerVideoRenderer::QGstreamerVideoRenderer(QObject *parent) + :QVideoRendererControl(parent),m_videoSink(0), m_surface(0) +{ +} + +QGstreamerVideoRenderer::~QGstreamerVideoRenderer() +{ + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); +} + +GstElement *QGstreamerVideoRenderer::videoSink() +{ + if (!m_videoSink && m_surface) { + m_videoSink = QVideoSurfaceGstSink::createSink(m_surface); + gst_object_ref(GST_OBJECT(m_videoSink)); //Take ownership + gst_object_sink(GST_OBJECT(m_videoSink)); + } + + return reinterpret_cast<GstElement*>(m_videoSink); +} + + +QAbstractVideoSurface *QGstreamerVideoRenderer::surface() const +{ + return m_surface; +} + +void QGstreamerVideoRenderer::setSurface(QAbstractVideoSurface *surface) +{ + if (m_surface != surface) { + //qDebug() << Q_FUNC_INFO << surface; + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); + + m_videoSink = 0; + + if (m_surface) { + disconnect(m_surface, SIGNAL(supportedFormatsChanged()), + this, SLOT(handleFormatChange())); + } + + m_surface = surface; + + if (surface && !m_surface) + emit readyChanged(true); + + if (!surface && m_surface) + emit readyChanged(false); + + if (m_surface) { + connect(m_surface, SIGNAL(supportedFormatsChanged()), + this, SLOT(handleFormatChange())); + } + + emit sinkChanged(); + } +} + +void QGstreamerVideoRenderer::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(); +} diff --git a/src/plugins/gstreamer/qgstreamervideorenderer.h b/src/plugins/gstreamer/qgstreamervideorenderer.h new file mode 100644 index 000000000..b6c23ba51 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideorenderer.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEORENDERER_H +#define QGSTREAMERVIDEORENDERER_H + +#include <qvideorenderercontrol.h> +#include "qvideosurfacegstsink.h" + +#include "qgstreamervideorendererinterface.h" + +QT_USE_NAMESPACE + +class QGstreamerVideoRenderer : public QVideoRendererControl, public QGstreamerVideoRendererInterface +{ + Q_OBJECT + Q_INTERFACES(QGstreamerVideoRendererInterface) +public: + QGstreamerVideoRenderer(QObject *parent = 0); + virtual ~QGstreamerVideoRenderer(); + + QAbstractVideoSurface *surface() const; + void setSurface(QAbstractVideoSurface *surface); + + GstElement *videoSink(); + void precessNewStream() {} + + bool isReady() const { return m_surface != 0; } + +signals: + void sinkChanged(); + void readyChanged(bool); + +private slots: + void handleFormatChange(); + +private: + QVideoSurfaceGstSink *m_videoSink; + QAbstractVideoSurface *m_surface; +}; + +#endif // QGSTREAMERVIDEORENDRER_H diff --git a/src/plugins/gstreamer/qgstreamervideorendererinterface.cpp b/src/plugins/gstreamer/qgstreamervideorendererinterface.cpp new file mode 100644 index 000000000..b8358ba1c --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideorendererinterface.cpp @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideorendererinterface.h" + +QGstreamerVideoRendererInterface::~QGstreamerVideoRendererInterface() +{ +} diff --git a/src/plugins/gstreamer/qgstreamervideorendererinterface.h b/src/plugins/gstreamer/qgstreamervideorendererinterface.h new file mode 100644 index 000000000..08b046d9b --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideorendererinterface.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOOUTPUTCONTROL_H +#define QGSTREAMERVIDEOOUTPUTCONTROL_H + +#include <gst/gst.h> + +#include <QtCore/qobject.h> + +class QGstreamerVideoRendererInterface +{ +public: + virtual ~QGstreamerVideoRendererInterface(); + virtual GstElement *videoSink() = 0; + virtual void precessNewStream() {} + + //stopRenderer() is called when the renderer element is stopped. + //it can be reimplemented when video renderer can't detect + //changes to NULL state but has to free video resources. + virtual void stopRenderer() {} + + //the video output is configured, usually after the first paint event + //(winId is known, + virtual bool isReady() const { return true; } + + //video renderer may handle video sink specific gstreamer messages. + virtual void handleBusMessage(GstMessage*) {}; + virtual void handleSyncMessage(GstMessage*) {}; + + //signals: + //void sinkChanged(); + //void readyChanged(bool); +}; + +#define QGstreamerVideoRendererInterface_iid "com.nokia.Qt.QGstreamerVideoRendererInterface/1.0" +QT_BEGIN_NAMESPACE +Q_DECLARE_INTERFACE(QGstreamerVideoRendererInterface, QGstreamerVideoRendererInterface_iid) +QT_END_NAMESPACE + +#endif diff --git a/src/plugins/gstreamer/qgstreamervideowidget.cpp b/src/plugins/gstreamer/qgstreamervideowidget.cpp new file mode 100644 index 000000000..38fa5ba8e --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideowidget.cpp @@ -0,0 +1,331 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideowidget.h" +#include "qgstutils.h" + +#include <QtCore/qcoreevent.h> +#include <QtCore/qdebug.h> +#include <QtGui/qapplication.h> +#include <QtGui/qpainter.h> + +#ifdef Q_WS_X11 +# include <X11/Xlib.h> +#endif +#include <gst/gst.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/propertyprobe.h> + +class QGstreamerVideoWidget : public QWidget +{ +public: + QGstreamerVideoWidget(QWidget *parent = 0) + :QWidget(parent) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + QPalette palette; + palette.setColor(QPalette::Background, Qt::black); + setPalette(palette); + } + + virtual ~QGstreamerVideoWidget() {} + + QSize sizeHint() const + { + return m_nativeSize; + } + + void setNativeSize( const QSize &size) + { + if (size != m_nativeSize) { + m_nativeSize = size; + if (size.isEmpty()) + setMinimumSize(0,0); + else + setMinimumSize(160,120); + + updateGeometry(); + } + } + +protected: + void paintEvent(QPaintEvent *) + { + QPainter painter(this); + painter.fillRect(rect(), palette().background()); + } + + QSize m_nativeSize; +}; + +QGstreamerVideoWidgetControl::QGstreamerVideoWidgetControl(QObject *parent) + : QVideoWidgetControl(parent) + , m_videoSink(0) + , m_widget(0) + , m_fullScreen(false) +{ +} + +QGstreamerVideoWidgetControl::~QGstreamerVideoWidgetControl() +{ + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); + + delete m_widget; +} + +void QGstreamerVideoWidgetControl::createVideoWidget() +{ + if (m_widget) + return; + + m_widget = new QGstreamerVideoWidget; + + m_widget->installEventFilter(this); + m_windowId = m_widget->winId(); + + m_videoSink = gst_element_factory_make ("xvimagesink", NULL); + if (m_videoSink) { + // Check if the xv sink is usable + if (gst_element_set_state(m_videoSink, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS) { + gst_object_unref(GST_OBJECT(m_videoSink)); + m_videoSink = 0; + } else { + gst_element_set_state(m_videoSink, GST_STATE_NULL); + + g_object_set(G_OBJECT(m_videoSink), "force-aspect-ratio", 1, (const char*)NULL); +#ifdef Q_WS_MAEMO_5 + //the overlay xvideo adapter fails to switch winId, + //use "SGX Textured Video" adapter instead + g_object_set(G_OBJECT(m_videoSink), "device", "1", NULL); +#endif + } + } + + if (!m_videoSink) + m_videoSink = gst_element_factory_make ("ximagesink", NULL); + + gst_object_ref (GST_OBJECT (m_videoSink)); //Take ownership + gst_object_sink (GST_OBJECT (m_videoSink)); + + +} + +GstElement *QGstreamerVideoWidgetControl::videoSink() +{ + createVideoWidget(); + return m_videoSink; +} + +bool QGstreamerVideoWidgetControl::eventFilter(QObject *object, QEvent *e) +{ + if (m_widget && object == m_widget) { + if (e->type() == QEvent::ParentChange || e->type() == QEvent::Show) { + WId newWId = m_widget->winId(); + if (newWId != m_windowId) { + m_windowId = newWId; + // Even if we have created a winId at this point, other X applications + // need to be aware of it. + QApplication::syncX(); + setOverlay(); + } + } + + if (e->type() == QEvent::Show) { + // Setting these values ensures smooth resizing since it + // will prevent the system from clearing the background + m_widget->setAttribute(Qt::WA_NoSystemBackground, true); + m_widget->setAttribute(Qt::WA_PaintOnScreen, true); + } else if (e->type() == QEvent::Resize) { + // This is a workaround for missing background repaints + // when reducing window size + windowExposed(); + } + } + + return false; +} + +void QGstreamerVideoWidgetControl::precessNewStream() +{ + setOverlay(); + QMetaObject::invokeMethod(this, "updateNativeVideoSize", Qt::QueuedConnection); +} + +void QGstreamerVideoWidgetControl::setOverlay() +{ + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_windowId); + } +} + +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); + + if (caps) { + m_widget->setNativeSize(QGstUtils::capsCorrectedResolution(caps)); + gst_caps_unref(caps); + } + } else { + if (m_widget) + m_widget->setNativeSize(QSize()); + } +} + + +void QGstreamerVideoWidgetControl::windowExposed() +{ + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) + gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink)); +} + +QWidget *QGstreamerVideoWidgetControl::videoWidget() +{ + createVideoWidget(); + return m_widget; +} + +Qt::AspectRatioMode QGstreamerVideoWidgetControl::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void QGstreamerVideoWidgetControl::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + if (m_videoSink) { + g_object_set(G_OBJECT(m_videoSink), + "force-aspect-ratio", + (mode == Qt::KeepAspectRatio), + (const char*)NULL); + } + + m_aspectRatioMode = mode; +} + +bool QGstreamerVideoWidgetControl::isFullScreen() const +{ + return m_fullScreen; +} + +void QGstreamerVideoWidgetControl::setFullScreen(bool fullScreen) +{ + emit fullScreenChanged(m_fullScreen = fullScreen); +} + +int QGstreamerVideoWidgetControl::brightness() const +{ + int brightness = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness")) + g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, NULL); + + return brightness / 10; +} + +void QGstreamerVideoWidgetControl::setBrightness(int brightness) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness")) { + g_object_set(G_OBJECT(m_videoSink), "brightness", brightness * 10, NULL); + + emit brightnessChanged(brightness); + } +} + +int QGstreamerVideoWidgetControl::contrast() const +{ + int contrast = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast")) + g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, NULL); + + return contrast / 10; +} + +void QGstreamerVideoWidgetControl::setContrast(int contrast) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast")) { + g_object_set(G_OBJECT(m_videoSink), "contrast", contrast * 10, NULL); + + emit contrastChanged(contrast); + } +} + +int QGstreamerVideoWidgetControl::hue() const +{ + int hue = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue")) + g_object_get(G_OBJECT(m_videoSink), "hue", &hue, NULL); + + return hue / 10; +} + +void QGstreamerVideoWidgetControl::setHue(int hue) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue")) { + g_object_set(G_OBJECT(m_videoSink), "hue", hue * 10, NULL); + + emit hueChanged(hue); + } +} + +int QGstreamerVideoWidgetControl::saturation() const +{ + int saturation = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation")) + g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, NULL); + + return saturation / 10; +} + +void QGstreamerVideoWidgetControl::setSaturation(int saturation) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation")) { + g_object_set(G_OBJECT(m_videoSink), "saturation", saturation * 10, NULL); + + emit saturationChanged(saturation); + } +} diff --git a/src/plugins/gstreamer/qgstreamervideowidget.h b/src/plugins/gstreamer/qgstreamervideowidget.h new file mode 100644 index 000000000..3342351d1 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideowidget.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOWIDGET_H +#define QGSTREAMERVIDEOWIDGET_H + +#include <qvideowidgetcontrol.h> + +#include "qgstreamervideorendererinterface.h" + +QT_USE_NAMESPACE + +class QGstreamerVideoWidget; + +class QGstreamerVideoWidgetControl + : public QVideoWidgetControl + , public QGstreamerVideoRendererInterface +{ + Q_OBJECT + Q_INTERFACES(QGstreamerVideoRendererInterface) +public: + QGstreamerVideoWidgetControl(QObject *parent = 0); + virtual ~QGstreamerVideoWidgetControl(); + + GstElement *videoSink(); + void precessNewStream(); + + QWidget *videoWidget(); + + Qt::AspectRatioMode aspectRatioMode() const; + void setAspectRatioMode(Qt::AspectRatioMode mode); + + bool isFullScreen() const; + void setFullScreen(bool fullScreen); + + int brightness() const; + void setBrightness(int brightness); + + int contrast() const; + void setContrast(int contrast); + + int hue() const; + void setHue(int hue); + + int saturation() const; + void setSaturation(int saturation); + + void setOverlay(); + + bool eventFilter(QObject *object, QEvent *event); + +public slots: + void updateNativeVideoSize(); + +signals: + void sinkChanged(); + void readyChanged(bool); + +private: + void createVideoWidget(); + void windowExposed(); + + GstElement *m_videoSink; + QGstreamerVideoWidget *m_widget; + WId m_windowId; + Qt::AspectRatioMode m_aspectRatioMode; + bool m_fullScreen; +}; + +#endif // QGSTREAMERVIDEOWIDGET_H diff --git a/src/plugins/gstreamer/qgstreamervideowindow.cpp b/src/plugins/gstreamer/qgstreamervideowindow.cpp new file mode 100644 index 000000000..565000176 --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideowindow.cpp @@ -0,0 +1,342 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamervideowindow.h" +#include "qgstutils.h" + +#include <QtCore/qdebug.h> + +#include <gst/gst.h> +#include <gst/interfaces/xoverlay.h> +#include <gst/interfaces/propertyprobe.h> + + +#ifndef QT_NO_XVIDEO + +/* + QGstreamerVideoWindow is similar to QGstreamerVideoOverlay, + but uses xvimagesink like gstreamer element instead of QX11VideoSurface. + + This allows to use the accelerated elements if available on the target platform, + but requires at least 0.10.29 gstreamer version + with gst_x_overlay_set_render_rectangle to set display rect. +*/ + +QGstreamerVideoWindow::QGstreamerVideoWindow(QObject *parent, const char *elementName) + : QVideoWindowControl(parent) + , m_videoSink(0) + , m_windowId(0) + , m_aspectRatioMode(Qt::KeepAspectRatio) + , m_fullScreen(false) + , m_colorKey(QColor::Invalid) +{ + if (elementName) + m_videoSink = gst_element_factory_make(elementName, NULL); + else + m_videoSink = gst_element_factory_make("xvimagesink", NULL); + + if (m_videoSink) { + gst_object_ref(GST_OBJECT(m_videoSink)); //Take ownership + gst_object_sink(GST_OBJECT(m_videoSink)); + + GstPad *pad = gst_element_get_static_pad(m_videoSink,"sink"); + m_bufferProbeId = gst_pad_add_buffer_probe(pad, G_CALLBACK(padBufferProbe), this); + } +} + +QGstreamerVideoWindow::~QGstreamerVideoWindow() +{ + if (m_videoSink) + gst_object_unref(GST_OBJECT(m_videoSink)); +} + +WId QGstreamerVideoWindow::winId() const +{ + return m_windowId; +} + +void QGstreamerVideoWindow::setWinId(WId id) +{ + if (m_windowId == id) + return; + + qDebug() << Q_FUNC_INFO << id; + + WId oldId = m_windowId; + + m_windowId = id; + + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { + gst_x_overlay_set_xwindow_id(GST_X_OVERLAY(m_videoSink), m_windowId); + } + + if (!oldId) + emit readyChanged(true); + + if (!id) + emit readyChanged(false); +} + +void QGstreamerVideoWindow::precessNewStream() +{ + if (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); + } +} + +QRect QGstreamerVideoWindow::displayRect() const +{ + return m_displayRect; +} + +void QGstreamerVideoWindow::setDisplayRect(const QRect &rect) +{ + m_displayRect = rect; + + if (m_videoSink && GST_IS_X_OVERLAY(m_videoSink)) { +#if GST_VERSION_MICRO >= 29 + if (m_displayRect.isEmpty()) + gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), -1, -1, -1, -1); + else + gst_x_overlay_set_render_rectangle(GST_X_OVERLAY(m_videoSink), + m_displayRect.x(), + m_displayRect.y(), + m_displayRect.width(), + m_displayRect.height()); + repaint(); +#endif + } +} + +Qt::AspectRatioMode QGstreamerVideoWindow::aspectRatioMode() const +{ + return m_aspectRatioMode; +} + +void QGstreamerVideoWindow::setAspectRatioMode(Qt::AspectRatioMode mode) +{ + m_aspectRatioMode = mode; + + if (m_videoSink) { + g_object_set(G_OBJECT(m_videoSink), + "force-aspect-ratio", + (m_aspectRatioMode == Qt::KeepAspectRatio), + (const char*)NULL); + } +} + +void QGstreamerVideoWindow::repaint() +{ + 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; + GstStateChangeReturn res = gst_element_get_state(m_videoSink, &state, NULL, 1000000); + if (res != GST_STATE_CHANGE_FAILURE && state != GST_STATE_NULL) { + gst_x_overlay_expose(GST_X_OVERLAY(m_videoSink)); + } + } +} + +QColor QGstreamerVideoWindow::colorKey() const +{ + if (!m_colorKey.isValid()) { + gint colorkey = 0; + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "colorkey")) + g_object_get(G_OBJECT(m_videoSink), "colorkey", &colorkey, NULL); + + if (colorkey > 0) + m_colorKey.setRgb(colorkey); + } + + return m_colorKey; +} + +void QGstreamerVideoWindow::setColorKey(const QColor &color) +{ + m_colorKey = color; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "colorkey")) + g_object_set(G_OBJECT(m_videoSink), "colorkey", color.rgba(), NULL); +} + +bool QGstreamerVideoWindow::autopaintColorKey() const +{ + bool enabled = true; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "autopaint-colorkey")) + g_object_get(G_OBJECT(m_videoSink), "autopaint-colorkey", &enabled, NULL); + + return enabled; +} + +void QGstreamerVideoWindow::setAutopaintColorKey(bool enabled) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "autopaint-colorkey")) + g_object_set(G_OBJECT(m_videoSink), "autopaint-colorkey", enabled, NULL); +} + +int QGstreamerVideoWindow::brightness() const +{ + int brightness = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness")) + g_object_get(G_OBJECT(m_videoSink), "brightness", &brightness, NULL); + + return brightness / 10; +} + +void QGstreamerVideoWindow::setBrightness(int brightness) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "brightness")) { + g_object_set(G_OBJECT(m_videoSink), "brightness", brightness * 10, NULL); + + emit brightnessChanged(brightness); + } +} + +int QGstreamerVideoWindow::contrast() const +{ + int contrast = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast")) + g_object_get(G_OBJECT(m_videoSink), "contrast", &contrast, NULL); + + return contrast / 10; +} + +void QGstreamerVideoWindow::setContrast(int contrast) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "contrast")) { + g_object_set(G_OBJECT(m_videoSink), "contrast", contrast * 10, NULL); + + emit contrastChanged(contrast); + } +} + +int QGstreamerVideoWindow::hue() const +{ + int hue = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue")) + g_object_get(G_OBJECT(m_videoSink), "hue", &hue, NULL); + + return hue / 10; +} + +void QGstreamerVideoWindow::setHue(int hue) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "hue")) { + g_object_set(G_OBJECT(m_videoSink), "hue", hue * 10, NULL); + + emit hueChanged(hue); + } +} + +int QGstreamerVideoWindow::saturation() const +{ + int saturation = 0; + + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation")) + g_object_get(G_OBJECT(m_videoSink), "saturation", &saturation, NULL); + + return saturation / 10; +} + +void QGstreamerVideoWindow::setSaturation(int saturation) +{ + if (m_videoSink && g_object_class_find_property(G_OBJECT_GET_CLASS(m_videoSink), "saturation")) { + g_object_set(G_OBJECT(m_videoSink), "saturation", saturation * 10, NULL); + + emit saturationChanged(saturation); + } +} + +bool QGstreamerVideoWindow::isFullScreen() const +{ + return m_fullScreen; +} + +void QGstreamerVideoWindow::setFullScreen(bool fullScreen) +{ + emit fullScreenChanged(m_fullScreen = fullScreen); +} + +QSize QGstreamerVideoWindow::nativeSize() const +{ + return m_nativeSize; +} + +void QGstreamerVideoWindow::padBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data) +{ + QGstreamerVideoWindow *control = reinterpret_cast<QGstreamerVideoWindow*>(user_data); + QMetaObject::invokeMethod(control, "updateNativeVideoSize", Qt::QueuedConnection); + gst_pad_remove_buffer_probe(pad, control->m_bufferProbeId); +} + +void QGstreamerVideoWindow::updateNativeVideoSize() +{ + 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); + + if (caps) { + m_nativeSize = QGstUtils::capsCorrectedResolution(caps); + gst_caps_unref(caps); + } + } + + if (m_nativeSize != oldSize) + emit nativeSizeChanged(); +} + +GstElement *QGstreamerVideoWindow::videoSink() +{ + return m_videoSink; +} + +#endif //QT_NO_XVIDEO diff --git a/src/plugins/gstreamer/qgstreamervideowindow.h b/src/plugins/gstreamer/qgstreamervideowindow.h new file mode 100644 index 000000000..e2229ae9e --- /dev/null +++ b/src/plugins/gstreamer/qgstreamervideowindow.h @@ -0,0 +1,131 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTREAMERVIDEOWINDOW_H +#define QGSTREAMERVIDEOWINDOW_H + +#include <qvideowindowcontrol.h> + +#include "qgstreamervideorendererinterface.h" + +QT_BEGIN_NAMESPACE +class QAbstractVideoSurface; +QT_END_NAMESPACE +class QX11VideoSurface; + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + +QT_USE_NAMESPACE + +class QGstreamerVideoWindow : public QVideoWindowControl, public QGstreamerVideoRendererInterface +{ + Q_OBJECT + Q_INTERFACES(QGstreamerVideoRendererInterface) + Q_PROPERTY(QColor colorKey READ colorKey WRITE setColorKey) + Q_PROPERTY(bool autopaintColorKey READ autopaintColorKey WRITE setAutopaintColorKey) +public: + QGstreamerVideoWindow(QObject *parent = 0, const char *elementName = 0); + ~QGstreamerVideoWindow(); + + WId winId() const; + void setWinId(WId id); + + QRect displayRect() const; + void setDisplayRect(const QRect &rect); + + bool isFullScreen() const; + void setFullScreen(bool fullScreen); + + QSize nativeSize() const; + + Qt::AspectRatioMode aspectRatioMode() const; + void setAspectRatioMode(Qt::AspectRatioMode mode); + + QColor colorKey() const; + void setColorKey(const QColor &); + + bool autopaintColorKey() const; + void setAutopaintColorKey(bool); + + void repaint(); + + int brightness() const; + void setBrightness(int brightness); + + int contrast() const; + void setContrast(int contrast); + + int hue() const; + void setHue(int hue); + + int saturation() const; + void setSaturation(int saturation); + + QAbstractVideoSurface *surface() const; + + GstElement *videoSink(); + + void precessNewStream(); + bool isReady() const { return m_windowId != 0; } + +signals: + void sinkChanged(); + void readyChanged(bool); + +private slots: + void updateNativeVideoSize(); + +private: + static void padBufferProbe(GstPad *pad, GstBuffer *buffer, gpointer user_data); + + GstElement *m_videoSink; + WId m_windowId; + Qt::AspectRatioMode m_aspectRatioMode; + QRect m_displayRect; + bool m_fullScreen; + QSize m_nativeSize; + mutable QColor m_colorKey; + int m_bufferProbeId; +}; + +#endif //QT_NO_XVIDEO + +#endif diff --git a/src/plugins/gstreamer/qgstutils.cpp b/src/plugins/gstreamer/qgstutils.cpp new file mode 100644 index 000000000..a0f2a9832 --- /dev/null +++ b/src/plugins/gstreamer/qgstutils.cpp @@ -0,0 +1,165 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstutils.h" + +#include <QtCore/qdatetime.h> +#include <QtCore/qbytearray.h> +#include <QtCore/qvariant.h> +#include <QtCore/qsize.h> + +//internal +static void addTagToMap(const GstTagList *list, + const gchar *tag, + gpointer user_data) +{ + QMap<QByteArray, QVariant> *map = reinterpret_cast<QMap<QByteArray, QVariant>* >(user_data); + + GValue val; + val.g_type = 0; + gst_tag_list_copy_value(&val,list,tag); + + switch( G_VALUE_TYPE(&val) ) { + case G_TYPE_STRING: + { + const gchar *str_value = g_value_get_string(&val); + map->insert(QByteArray(tag), QString::fromUtf8(str_value)); + break; + } + case G_TYPE_INT: + map->insert(QByteArray(tag), g_value_get_int(&val)); + break; + case G_TYPE_UINT: + map->insert(QByteArray(tag), g_value_get_uint(&val)); + break; + case G_TYPE_LONG: + map->insert(QByteArray(tag), qint64(g_value_get_long(&val))); + break; + case G_TYPE_BOOLEAN: + map->insert(QByteArray(tag), g_value_get_boolean(&val)); + break; + case G_TYPE_CHAR: + map->insert(QByteArray(tag), g_value_get_char(&val)); + 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 (G_VALUE_TYPE(&val) == GST_TYPE_DATE) { + const GDate *date = gst_value_get_date(&val); + if (g_date_valid(date)) { + int year = g_date_get_year(date); + int month = g_date_get_month(date); + int day = g_date_get_day(date); + map->insert(QByteArray(tag), QDate(year,month,day)); + if (!map->contains("year")) + map->insert("year", year); + } + } else if (G_VALUE_TYPE(&val) == GST_TYPE_FRACTION) { + int nom = gst_value_get_fraction_numerator(&val); + int denom = gst_value_get_fraction_denominator(&val); + + if (denom > 0) { + map->insert(QByteArray(tag), double(nom)/denom); + } + } + break; + } + + g_value_unset(&val); +} + +/*! + Convert GstTagList structure to QMap<QByteArray, QVariant>. + + Mapping to int, bool, char, string, fractions and date are supported. + Fraction values are converted to doubles. +*/ +QMap<QByteArray, QVariant> QGstUtils::gstTagListToMap(const GstTagList *tags) +{ + QMap<QByteArray, QVariant> res; + gst_tag_list_foreach(tags, addTagToMap, &res); + + return res; +} + +/*! + Returns resolution of \a caps. + If caps doesn't have a valid size, and ampty QSize is returned. +*/ +QSize QGstUtils::capsResolution(const GstCaps *caps) +{ + QSize size; + + if (caps) { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + gst_structure_get_int(structure, "width", &size.rwidth()); + gst_structure_get_int(structure, "height", &size.rheight()); + } + + return size; +} + +/*! + Returns aspect ratio corrected resolution of \a caps. + If caps doesn't have a valid size, and ampty QSize is returned. +*/ +QSize QGstUtils::capsCorrectedResolution(const GstCaps *caps) +{ + QSize size; + + if (caps) { + const GstStructure *structure = gst_caps_get_structure(caps, 0); + gst_structure_get_int(structure, "width", &size.rwidth()); + gst_structure_get_int(structure, "height", &size.rheight()); + + gint aspectNum = 0; + gint aspectDenum = 0; + if (!size.isEmpty() && gst_structure_get_fraction( + structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { + if (aspectDenum > 0) + size.setWidth(qRound(size.width()*aspectNum/aspectDenum)); + } + } + + return size; +} diff --git a/src/plugins/gstreamer/qgstutils.h b/src/plugins/gstreamer/qgstutils.h new file mode 100644 index 000000000..396b32b98 --- /dev/null +++ b/src/plugins/gstreamer/qgstutils.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTUTILS_H +#define QGSTUTILS_H + +#include <QtCore/qmap.h> +#include <gst/gst.h> + +class QSize; +class QVariant; +class QByteArray; + +namespace QGstUtils { + QMap<QByteArray, QVariant> gstTagListToMap(const GstTagList *list); + + QSize capsResolution(const GstCaps *caps); + QSize capsCorrectedResolution(const GstCaps *caps); +} + +#endif diff --git a/src/plugins/gstreamer/qgstvideobuffer.cpp b/src/plugins/gstreamer/qgstvideobuffer.cpp new file mode 100644 index 000000000..4ea6a7004 --- /dev/null +++ b/src/plugins/gstreamer/qgstvideobuffer.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstvideobuffer.h" + + +QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine) + : QAbstractVideoBuffer(NoHandle) + , m_buffer(buffer) + , m_bytesPerLine(bytesPerLine) + , m_mode(NotMapped) +{ + gst_buffer_ref(m_buffer); +} + +QGstVideoBuffer::QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine, + QGstVideoBuffer::HandleType handleType, + const QVariant &handle) + : QAbstractVideoBuffer(handleType) + , m_buffer(buffer) + , m_bytesPerLine(bytesPerLine) + , m_mode(NotMapped) + , m_handle(handle) +{ + gst_buffer_ref(m_buffer); +} + +QGstVideoBuffer::~QGstVideoBuffer() +{ + gst_buffer_unref(m_buffer); +} + + +QAbstractVideoBuffer::MapMode QGstVideoBuffer::mapMode() const +{ + return m_mode; +} + +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; + + m_mode = mode; + + return m_buffer->data; + } else { + return 0; + } +} +void QGstVideoBuffer::unmap() +{ + m_mode = NotMapped; +} + diff --git a/src/plugins/gstreamer/qgstvideobuffer.h b/src/plugins/gstreamer/qgstvideobuffer.h new file mode 100644 index 000000000..8d5a36e53 --- /dev/null +++ b/src/plugins/gstreamer/qgstvideobuffer.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTVIDEOBUFFER_H +#define QGSTVIDEOBUFFER_H + +#include <qabstractvideobuffer.h> +#include <QtCore/qvariant.h> + +#include <gst/gst.h> + +class QGstVideoBuffer : public QAbstractVideoBuffer +{ +public: + QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine); + QGstVideoBuffer(GstBuffer *buffer, int bytesPerLine, + HandleType handleType, const QVariant &handle); + ~QGstVideoBuffer(); + + MapMode mapMode() const; + + uchar *map(MapMode mode, int *numBytes, int *bytesPerLine); + void unmap(); + + QVariant handle() const { return m_handle; } +private: + GstBuffer *m_buffer; + int m_bytesPerLine; + MapMode m_mode; + QVariant m_handle; +}; + + +#endif diff --git a/src/plugins/gstreamer/qgstxvimagebuffer.cpp b/src/plugins/gstreamer/qgstxvimagebuffer.cpp new file mode 100644 index 000000000..393456291 --- /dev/null +++ b/src/plugins/gstreamer/qgstxvimagebuffer.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qdebug.h> +#include <QtCore/qthread.h> +#include <QtCore/qvariant.h> +#include <QtGui/qx11info_x11.h> + +#include "qgstxvimagebuffer.h" +#include "qvideosurfacegstsink.h" +#include "qgstvideobuffer.h" + +#ifndef QT_NO_XVIDEO + +GstBufferClass *QGstXvImageBuffer::parent_class = NULL; + +GType QGstXvImageBuffer::get_type(void) +{ + static GType buffer_type = 0; + + if (buffer_type == 0) { + static const GTypeInfo buffer_info = { + sizeof (GstBufferClass), + NULL, + NULL, + QGstXvImageBuffer::class_init, + NULL, + NULL, + sizeof(QGstXvImageBuffer), + 0, + (GInstanceInitFunc)QGstXvImageBuffer::buffer_init, + NULL + }; + buffer_type = g_type_register_static(GST_TYPE_BUFFER, + "QGstXvImageBuffer", &buffer_info, GTypeFlags(0)); + } + return buffer_type; +} + +void QGstXvImageBuffer::class_init(gpointer g_class, gpointer class_data) +{ + Q_UNUSED(class_data); + GST_MINI_OBJECT_CLASS(g_class)->finalize = + (GstMiniObjectFinalizeFunction)buffer_finalize; + parent_class = (GstBufferClass*)g_type_class_peek_parent(g_class); +} + +void QGstXvImageBuffer::buffer_init(QGstXvImageBuffer *xvImage, gpointer g_class) +{ + Q_UNUSED(g_class); + xvImage->pool = 0; + xvImage->shmInfo.shmaddr = ((char *) -1); + xvImage->shmInfo.shmid = -1; + xvImage->markedForDeletion = false; +} + +void QGstXvImageBuffer::buffer_finalize(QGstXvImageBuffer * xvImage) +{ + if (xvImage->pool) { + if (xvImage->markedForDeletion) + xvImage->pool->destroyBuffer(xvImage); + else + xvImage->pool->recycleBuffer(xvImage); + } +} + + +QGstXvImageBufferPool::QGstXvImageBufferPool(QObject *parent) + :QObject(parent) +{ + m_threadId = QThread::currentThreadId(); +} + +QGstXvImageBufferPool::~QGstXvImageBufferPool() +{ +} + +bool QGstXvImageBufferPool::isFormatSupported(const QVideoSurfaceFormat &surfaceFormat) const +{ + bool ok = true; + surfaceFormat.property("portId").toULongLong(&ok); + if (!ok) + return false; + + int xvFormatId = surfaceFormat.property("xvFormatId").toInt(&ok); + if (!ok || xvFormatId < 0) + return false; + + int dataSize = surfaceFormat.property("dataSize").toInt(&ok); + if (!ok || dataSize<=0) + return false; + + return true; +} + +GType QGstXvImageBufferPool::bufferType() const +{ + return QGstXvImageBuffer::get_type(); +} + +GstBuffer *QGstXvImageBufferPool::takeBuffer( + const QVideoSurfaceFormat &format, GstCaps *caps) +{ + m_poolMutex.lock(); + + m_caps = caps; + if (format != m_format) { + doClear(); + m_format = format; + } + + + if (m_pool.isEmpty()) { + //qDebug() << "QGstXvImageBufferPool::takeBuffer: no buffer available, allocate the new one" << QThread::currentThreadId() << m_threadId; + if (QThread::currentThreadId() == m_threadId) { + doAlloc(); + } else { + QMetaObject::invokeMethod(this, "queuedAlloc", Qt::QueuedConnection); + m_allocWaitCondition.wait(&m_poolMutex, 300); + } + } + QGstXvImageBuffer *res = 0; + + if (!m_pool.isEmpty()) { + res = m_pool.takeLast(); + } + + m_poolMutex.unlock(); + + return GST_BUFFER(res); +} + +QAbstractVideoBuffer::HandleType QGstXvImageBufferPool::handleType() const +{ + return QAbstractVideoBuffer::XvShmImageHandle; +} + +QAbstractVideoBuffer *QGstXvImageBufferPool::prepareVideoBuffer(GstBuffer *buffer, int bytesPerLine) +{ + QGstXvImageBuffer *xvBuffer = reinterpret_cast<QGstXvImageBuffer *>(buffer); + QVariant handle = QVariant::fromValue(xvBuffer->xvImage); + return new QGstVideoBuffer(buffer, bytesPerLine, QAbstractVideoBuffer::XvShmImageHandle, handle); +} + +void QGstXvImageBufferPool::queuedAlloc() +{ + QMutexLocker lock(&m_poolMutex); + doAlloc(); + m_allocWaitCondition.wakeOne(); +} + +void QGstXvImageBufferPool::doAlloc() +{ + //should be always called from the main thread with m_poolMutex locked + //Q_ASSERT(QThread::currentThread() == thread()); + + XSync(QX11Info::display(), false); + + QGstXvImageBuffer *xvBuffer = (QGstXvImageBuffer *)gst_mini_object_new(QGstXvImageBuffer::get_type()); + + quint64 portId = m_format.property("portId").toULongLong(); + int xvFormatId = m_format.property("xvFormatId").toInt(); + + xvBuffer->xvImage = XvShmCreateImage( + QX11Info::display(), + portId, + xvFormatId, + 0, + m_format.frameWidth(), + m_format.frameHeight(), + &xvBuffer->shmInfo + ); + + if (!xvBuffer->xvImage) { + qWarning() << "QGstXvImageBufferPool: XvShmCreateImage failed"; + return; + } + + XSync(QX11Info::display(), false); + + xvBuffer->shmInfo.shmid = shmget(IPC_PRIVATE, xvBuffer->xvImage->data_size, IPC_CREAT | 0777); + xvBuffer->shmInfo.shmaddr = xvBuffer->xvImage->data = (char*)shmat(xvBuffer->shmInfo.shmid, 0, 0); + xvBuffer->shmInfo.readOnly = False; + + if (!XShmAttach(QX11Info::display(), &xvBuffer->shmInfo)) { + qWarning() << "QGstXvImageBufferPool: XShmAttach failed"; + return; + } + + XSync(QX11Info::display(), false); + + shmctl (xvBuffer->shmInfo.shmid, IPC_RMID, NULL); + + xvBuffer->pool = this; + GST_MINI_OBJECT_CAST(xvBuffer)->flags = 0; + gst_buffer_set_caps(GST_BUFFER_CAST(xvBuffer), m_caps); + GST_BUFFER_DATA(xvBuffer) = (uchar*)xvBuffer->xvImage->data; + GST_BUFFER_SIZE(xvBuffer) = xvBuffer->xvImage->data_size; + + m_allBuffers.append(xvBuffer); + m_pool.append(xvBuffer); + + XSync(QX11Info::display(), false); +} + + +void QGstXvImageBufferPool::clear() +{ + QMutexLocker lock(&m_poolMutex); + doClear(); +} + +void QGstXvImageBufferPool::doClear() +{ + foreach (QGstXvImageBuffer *xvBuffer, m_allBuffers) { + xvBuffer->markedForDeletion = true; + } + m_allBuffers.clear(); + + foreach (QGstXvImageBuffer *xvBuffer, m_pool) { + gst_buffer_unref(GST_BUFFER(xvBuffer)); + } + m_pool.clear(); + + m_format = QVideoSurfaceFormat(); +} + +void QGstXvImageBufferPool::queuedDestroy() +{ + QMutexLocker lock(&m_destroyMutex); + + XSync(QX11Info::display(), false); + + foreach(XvShmImage xvImage, m_imagesToDestroy) { + if (xvImage.shmInfo.shmaddr != ((void *) -1)) { + XShmDetach(QX11Info::display(), &xvImage.shmInfo); + XSync(QX11Info::display(), false); + + shmdt(xvImage.shmInfo.shmaddr); + } + + if (xvImage.xvImage) + XFree(xvImage.xvImage); + } + + m_imagesToDestroy.clear(); + + XSync(QX11Info::display(), false); +} + +void QGstXvImageBufferPool::recycleBuffer(QGstXvImageBuffer *xvBuffer) +{ + QMutexLocker lock(&m_poolMutex); + gst_buffer_ref(GST_BUFFER_CAST(xvBuffer)); + m_pool.append(xvBuffer); +} + +void QGstXvImageBufferPool::destroyBuffer(QGstXvImageBuffer *xvBuffer) +{ + XvShmImage imageToDestroy; + imageToDestroy.xvImage = xvBuffer->xvImage; + imageToDestroy.shmInfo = xvBuffer->shmInfo; + + m_destroyMutex.lock(); + m_imagesToDestroy.append(imageToDestroy); + m_destroyMutex.unlock(); + + if (m_imagesToDestroy.size() == 1) + QMetaObject::invokeMethod(this, "queuedDestroy", Qt::QueuedConnection); +} + +#endif //QT_NO_XVIDEO + diff --git a/src/plugins/gstreamer/qgstxvimagebuffer.h b/src/plugins/gstreamer/qgstxvimagebuffer.h new file mode 100644 index 000000000..2ab5ea03b --- /dev/null +++ b/src/plugins/gstreamer/qgstxvimagebuffer.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGSTXVIMAGEBUFFER_H +#define QGSTXVIMAGEBUFFER_H + +#include <qabstractvideobuffer.h> +#include <qvideosurfaceformat.h> +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qqueue.h> + +#ifndef QT_NO_XVIDEO + +#include <X11/Xlib.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#include <X11/Xlib.h> +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> + + +#include <gst/gst.h> +#include "qabstractgstbufferpool.h" + +class QGstXvImageBufferPool; + +struct QGstXvImageBuffer { + GstBuffer buffer; + QGstXvImageBufferPool *pool; + XvImage *xvImage; + XShmSegmentInfo shmInfo; + bool markedForDeletion; + + static GType get_type(void); + static void class_init(gpointer g_class, gpointer class_data); + static void buffer_init(QGstXvImageBuffer *xvimage, gpointer g_class); + static void buffer_finalize(QGstXvImageBuffer * xvimage); + static GstBufferClass *parent_class; +}; + +Q_DECLARE_METATYPE(XvImage*) + +class QGstXvImageBufferPool : public QObject, public QAbstractGstBufferPool { +Q_OBJECT +friend class QGstXvImageBuffer; +public: + QGstXvImageBufferPool(QObject *parent = 0); + virtual ~QGstXvImageBufferPool(); + + bool isFormatSupported(const QVideoSurfaceFormat &format) const; + + GType bufferType() const; + GstBuffer *takeBuffer(const QVideoSurfaceFormat &format, GstCaps *caps); + void clear(); + + QAbstractVideoBuffer::HandleType handleType() const; + QAbstractVideoBuffer *prepareVideoBuffer(GstBuffer *buffer, int bytesPerLine); + +private slots: + void queuedAlloc(); + void queuedDestroy(); + + void doClear(); + + void recycleBuffer(QGstXvImageBuffer *); + void destroyBuffer(QGstXvImageBuffer *); + +private: + void doAlloc(); + + struct XvShmImage { + XvImage *xvImage; + XShmSegmentInfo shmInfo; + }; + + QMutex m_poolMutex; + QMutex m_allocMutex; + QWaitCondition m_allocWaitCondition; + QMutex m_destroyMutex; + QVideoSurfaceFormat m_format; + GstCaps *m_caps; + QList<QGstXvImageBuffer*> m_pool; + QList<QGstXvImageBuffer*> m_allBuffers; + QList<XvShmImage> m_imagesToDestroy; + Qt::HANDLE m_threadId; +}; + +#endif //QT_NO_XVIDEO + +#endif diff --git a/src/plugins/gstreamer/qvideosurfacegstsink.cpp b/src/plugins/gstreamer/qvideosurfacegstsink.cpp new file mode 100644 index 000000000..f3c0e3468 --- /dev/null +++ b/src/plugins/gstreamer/qvideosurfacegstsink.cpp @@ -0,0 +1,772 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qabstractvideosurface.h> +#include <qvideoframe.h> +#include <QDebug> +#include <QMap> +#include <QDebug> +#include <QThread> + +#include "qgstvideobuffer.h" + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) +#include <QtGui/qx11info_x11.h> +#include "qgstxvimagebuffer.h" +#endif + +#include "qvideosurfacegstsink.h" + +//#define DEBUG_VIDEO_SURFACE_SINK + + +Q_DECLARE_METATYPE(QVideoSurfaceFormat) + +QVideoSurfaceGstDelegate::QVideoSurfaceGstDelegate( + QAbstractVideoSurface *surface) + : m_surface(surface) + , m_pool(0) + , m_renderReturn(GST_FLOW_ERROR) + , m_bytesPerLine(0) +{ + if (m_surface) { +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) + m_pools.append(new QGstXvImageBufferPool()); +#endif + updateSupportedFormats(); + connect(m_surface, SIGNAL(supportedFormatsChanged()), this, SLOT(updateSupportedFormats())); + } +} + +QVideoSurfaceGstDelegate::~QVideoSurfaceGstDelegate() +{ + qDeleteAll(m_pools); +} + +QList<QVideoFrame::PixelFormat> QVideoSurfaceGstDelegate::supportedPixelFormats(QAbstractVideoBuffer::HandleType handleType) const +{ + QMutexLocker locker(const_cast<QMutex *>(&m_mutex)); + + if (!m_surface) + return QList<QVideoFrame::PixelFormat>(); + else if (handleType == QAbstractVideoBuffer::NoHandle) + return m_supportedPixelFormats; + else if (handleType == m_pool->handleType()) + return m_supportedPoolPixelFormats; + else + return m_surface->supportedPixelFormats(handleType); +} + +QVideoSurfaceFormat QVideoSurfaceGstDelegate::surfaceFormat() const +{ + QMutexLocker locker(const_cast<QMutex *>(&m_mutex)); + return m_format; +} + +bool QVideoSurfaceGstDelegate::start(const QVideoSurfaceFormat &format, int bytesPerLine) +{ + if (!m_surface) + return false; + + QMutexLocker locker(&m_mutex); + + m_format = format; + m_bytesPerLine = bytesPerLine; + + if (QThread::currentThread() == thread()) { + m_started = !m_surface.isNull() ? m_surface->start(m_format) : false; + } else { + QMetaObject::invokeMethod(this, "queuedStart", Qt::QueuedConnection); + + m_setupCondition.wait(&m_mutex); + } + + m_format = m_surface->surfaceFormat(); + + return m_started; +} + +void QVideoSurfaceGstDelegate::stop() +{ + if (!m_surface) + return; + + QMutexLocker locker(&m_mutex); + + if (QThread::currentThread() == thread()) { + if (!m_surface.isNull()) + m_surface->stop(); + } else { + QMetaObject::invokeMethod(this, "queuedStop", Qt::QueuedConnection); + + m_setupCondition.wait(&m_mutex); + } + + m_started = false; +} + +bool QVideoSurfaceGstDelegate::isActive() +{ + QMutexLocker locker(&m_mutex); + return !m_surface.isNull() && m_surface->isActive(); +} + +GstFlowReturn QVideoSurfaceGstDelegate::render(GstBuffer *buffer) +{ + if (!m_surface) { + qWarning() << "Rendering video frame to deleted surface, skip."; + //return GST_FLOW_NOT_NEGOTIATED; + return GST_FLOW_OK; + } + + QMutexLocker locker(&m_mutex); + + QAbstractVideoBuffer *videoBuffer = 0; + + if (m_pool && G_TYPE_CHECK_INSTANCE_TYPE(buffer, m_pool->bufferType())) + videoBuffer = m_pool->prepareVideoBuffer(buffer, m_bytesPerLine); + else + videoBuffer = new QGstVideoBuffer(buffer, m_bytesPerLine); + + m_frame = QVideoFrame( + videoBuffer, + m_format.frameSize(), + m_format.pixelFormat()); + + qint64 startTime = GST_BUFFER_TIMESTAMP(buffer); + + if (startTime >= 0) { + m_frame.setStartTime(startTime/G_GINT64_CONSTANT (1000000)); + + qint64 duration = GST_BUFFER_DURATION(buffer); + + if (duration >= 0) + m_frame.setEndTime((startTime + duration)/G_GINT64_CONSTANT (1000000)); + } + + QMetaObject::invokeMethod(this, "queuedRender", Qt::QueuedConnection); + + if (!m_renderCondition.wait(&m_mutex, 300)) { + m_frame = QVideoFrame(); + + return GST_FLOW_OK; + } else { + return m_renderReturn; + } +} + +void QVideoSurfaceGstDelegate::queuedStart() +{ + QMutexLocker locker(&m_mutex); + + m_started = m_surface->start(m_format); + + m_setupCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::queuedStop() +{ + QMutexLocker locker(&m_mutex); + + m_surface->stop(); + + m_setupCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::queuedRender() +{ + QMutexLocker locker(&m_mutex); + + if (m_surface.isNull()) { + qWarning() << "Rendering video frame to deleted surface, skip the frame"; + m_renderReturn = GST_FLOW_OK; + } else if (m_surface->present(m_frame)) { + m_renderReturn = GST_FLOW_OK; + } else { + switch (m_surface->error()) { + case QAbstractVideoSurface::NoError: + m_renderReturn = GST_FLOW_OK; + break; + case QAbstractVideoSurface::StoppedError: + //It's likely we are in process of changing video output + //and the surface is already stopped, ignore the frame + m_renderReturn = GST_FLOW_OK; + break; + default: + qWarning() << "Failed to render video frame:" << m_surface->error(); + m_renderReturn = GST_FLOW_OK; + break; + } + } + + m_renderCondition.wakeAll(); +} + +void QVideoSurfaceGstDelegate::updateSupportedFormats() +{ + QAbstractGstBufferPool *newPool = 0; + foreach (QAbstractGstBufferPool *pool, m_pools) { + if (!m_surface->supportedPixelFormats(pool->handleType()).isEmpty()) { + newPool = pool; + break; + } + } + + if (newPool != m_pool) { + QMutexLocker lock(&m_poolMutex); + + if (m_pool) + m_pool->clear(); + m_pool = newPool; + } + + QMutexLocker locker(&m_mutex); + + m_supportedPixelFormats.clear(); + m_supportedPoolPixelFormats.clear(); + if (m_surface) { + m_supportedPixelFormats = m_surface->supportedPixelFormats(); + if (m_pool) + m_supportedPoolPixelFormats = m_surface->supportedPixelFormats(m_pool->handleType()); + } +} + +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, 0xFF000000, 0x00000000 }, + { QVideoFrame::Format_RGB32 , 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, + { QVideoFrame::Format_BGR32 , 32, 24, 4321, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x00000000 }, + { QVideoFrame::Format_BGR32 , 32, 24, 1234, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, + { QVideoFrame::Format_ARGB32, 32, 24, 4321, 0x0000FF00, 0x00FF0000, 0xFF000000, 0x000000FF }, + { QVideoFrame::Format_ARGB32, 32, 24, 1234, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 }, + { QVideoFrame::Format_RGB24 , 24, 24, 4321, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 }, + { QVideoFrame::Format_BGR24 , 24, 24, 4321, 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 }, + { QVideoFrame::Format_RGB565, 16, 16, 1234, 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000 } +}; + +static int indexOfRgbColor( + int bits, int depth, int endianness, int red, int green, int blue, int alpha) +{ + const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); + + for (int i = 0; i < count; ++i) { + if (qt_rgbColorLookup[i].bitsPerPixel == bits + && qt_rgbColorLookup[i].depth == depth + && qt_rgbColorLookup[i].endianness == endianness + && qt_rgbColorLookup[i].red == red + && qt_rgbColorLookup[i].green == green + && qt_rgbColorLookup[i].blue == blue + && qt_rgbColorLookup[i].alpha == alpha) { + return i; + } + } + return -1; +} + +static GstVideoSinkClass *sink_parent_class; + +#define VO_SINK(s) QVideoSurfaceGstSink *sink(reinterpret_cast<QVideoSurfaceGstSink *>(s)) + +QVideoSurfaceGstSink *QVideoSurfaceGstSink::createSink(QAbstractVideoSurface *surface) +{ + QVideoSurfaceGstSink *sink = reinterpret_cast<QVideoSurfaceGstSink *>( + g_object_new(QVideoSurfaceGstSink::get_type(), 0)); + + sink->delegate = new QVideoSurfaceGstDelegate(surface); + + return sink; +} + +GType QVideoSurfaceGstSink::get_type() +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = + { + sizeof(QVideoSurfaceGstSinkClass), // class_size + base_init, // base_init + NULL, // base_finalize + class_init, // class_init + NULL, // class_finalize + NULL, // class_data + sizeof(QVideoSurfaceGstSink), // instance_size + 0, // n_preallocs + instance_init, // instance_init + 0 // value_table + }; + + type = g_type_register_static( + GST_TYPE_VIDEO_SINK, "QVideoSurfaceGstSink", &info, GTypeFlags(0)); + } + + return type; +} + +void QVideoSurfaceGstSink::class_init(gpointer g_class, gpointer class_data) +{ + Q_UNUSED(class_data); + + sink_parent_class = reinterpret_cast<GstVideoSinkClass *>(g_type_class_peek_parent(g_class)); + + GstBaseSinkClass *base_sink_class = reinterpret_cast<GstBaseSinkClass *>(g_class); + base_sink_class->get_caps = QVideoSurfaceGstSink::get_caps; + base_sink_class->set_caps = QVideoSurfaceGstSink::set_caps; + base_sink_class->buffer_alloc = QVideoSurfaceGstSink::buffer_alloc; + base_sink_class->start = QVideoSurfaceGstSink::start; + base_sink_class->stop = QVideoSurfaceGstSink::stop; + // base_sink_class->unlock = QVideoSurfaceGstSink::unlock; // Not implemented. + // base_sink_class->event = QVideoSurfaceGstSink::event; // Not implemented. + base_sink_class->preroll = QVideoSurfaceGstSink::preroll; + base_sink_class->render = QVideoSurfaceGstSink::render; + + GstElementClass *element_class = reinterpret_cast<GstElementClass *>(g_class); + element_class->change_state = QVideoSurfaceGstSink::change_state; + + GObjectClass *object_class = reinterpret_cast<GObjectClass *>(g_class); + object_class->finalize = QVideoSurfaceGstSink::finalize; +} + +void QVideoSurfaceGstSink::base_init(gpointer g_class) +{ + static GstStaticPadTemplate sink_pad_template = GST_STATIC_PAD_TEMPLATE( + "sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS( + "video/x-raw-rgb, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]; " + "video/x-raw-yuv, " + "framerate = (fraction) [ 0, MAX ], " + "width = (int) [ 1, MAX ], " + "height = (int) [ 1, MAX ]")); + + gst_element_class_add_pad_template( + GST_ELEMENT_CLASS(g_class), gst_static_pad_template_get(&sink_pad_template)); +} + +void QVideoSurfaceGstSink::instance_init(GTypeInstance *instance, gpointer g_class) +{ + VO_SINK(instance); + + Q_UNUSED(g_class); + + sink->delegate = 0; + + sink->lastRequestedCaps = 0; + sink->lastBufferCaps = 0; + sink->lastSurfaceFormat = new QVideoSurfaceFormat; +} + +void QVideoSurfaceGstSink::finalize(GObject *object) +{ + VO_SINK(object); + + delete sink->lastSurfaceFormat; + sink->lastSurfaceFormat = 0; + + if (sink->lastBufferCaps) + gst_caps_unref(sink->lastBufferCaps); + sink->lastBufferCaps = 0; + + if (sink->lastRequestedCaps) + gst_caps_unref(sink->lastRequestedCaps); + sink->lastRequestedCaps = 0; +} + +GstStateChangeReturn QVideoSurfaceGstSink::change_state( + GstElement *element, GstStateChange transition) +{ + Q_UNUSED(element); + + return GST_ELEMENT_CLASS(sink_parent_class)->change_state( + element, transition); +} + +GstCaps *QVideoSurfaceGstSink::get_caps(GstBaseSink *base) +{ + VO_SINK(base); + + GstCaps *caps = gst_caps_new_empty(); + + foreach (QVideoFrame::PixelFormat format, sink->delegate->supportedPixelFormats()) { + int index = indexOfYuvColor(format); + + if (index != -1) { + gst_caps_append_structure(caps, gst_structure_new( + "video/x-raw-yuv", + "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, + "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "format" , GST_TYPE_FOURCC, qt_yuvColorLookup[index].fourcc, + NULL)); + continue; + } + + const int count = sizeof(qt_rgbColorLookup) / sizeof(RgbFormat); + + for (int i = 0; i < count; ++i) { + if (qt_rgbColorLookup[i].pixelFormat == format) { + GstStructure *structure = gst_structure_new( + "video/x-raw-rgb", + "framerate" , GST_TYPE_FRACTION_RANGE, 0, 1, INT_MAX, 1, + "width" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "height" , GST_TYPE_INT_RANGE, 1, INT_MAX, + "bpp" , G_TYPE_INT, qt_rgbColorLookup[i].bitsPerPixel, + "depth" , G_TYPE_INT, qt_rgbColorLookup[i].depth, + "endianness", G_TYPE_INT, qt_rgbColorLookup[i].endianness, + "red_mask" , G_TYPE_INT, qt_rgbColorLookup[i].red, + "green_mask", G_TYPE_INT, qt_rgbColorLookup[i].green, + "blue_mask" , G_TYPE_INT, qt_rgbColorLookup[i].blue, + NULL); + + if (qt_rgbColorLookup[i].alpha != 0) { + gst_structure_set( + structure, "alpha_mask", G_TYPE_INT, qt_rgbColorLookup[i].alpha, NULL); + } + gst_caps_append_structure(caps, structure); + } + } + } + + return caps; +} + +gboolean QVideoSurfaceGstSink::set_caps(GstBaseSink *base, GstCaps *caps) +{ + VO_SINK(base); + +#ifdef DEBUG_VIDEO_SURFACE_SINK + qDebug() << "set_caps:"; + qDebug() << gst_caps_to_string(caps); +#endif + + if (!caps) { + sink->delegate->stop(); + + return TRUE; + } else { + int bytesPerLine = 0; + QVideoSurfaceFormat format = formatForCaps(caps, &bytesPerLine); + + if (sink->delegate->isActive()) { + QVideoSurfaceFormat surfaceFormst = sink->delegate->surfaceFormat(); + + if (format.pixelFormat() == surfaceFormst.pixelFormat() && + format.frameSize() == surfaceFormst.frameSize()) + return TRUE; + else + sink->delegate->stop(); + } + + if (sink->lastRequestedCaps) + gst_caps_unref(sink->lastRequestedCaps); + sink->lastRequestedCaps = 0; + +#ifdef DEBUG_VIDEO_SURFACE_SINK + qDebug() << "Staring video surface, format:"; + qDebug() << format; + qDebug() << "bytesPerLine:" << bytesPerLine; +#endif + + if (sink->delegate->start(format, bytesPerLine)) + return TRUE; + else + qWarning() << "Failed to start video surface"; + } + + return FALSE; +} + +QVideoSurfaceFormat QVideoSurfaceGstSink::formatForCaps(GstCaps *caps, int *bytesPerLine) +{ + const GstStructure *structure = gst_caps_get_structure(caps, 0); + + QVideoFrame::PixelFormat pixelFormat = QVideoFrame::Format_Invalid; + int bitsPerPixel = 0; + + QSize size; + gst_structure_get_int(structure, "width", &size.rwidth()); + gst_structure_get_int(structure, "height", &size.rheight()); + + if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-yuv") == 0) { + guint32 fourcc = 0; + gst_structure_get_fourcc(structure, "format", &fourcc); + + int index = indexOfYuvColor(fourcc); + if (index != -1) { + pixelFormat = qt_yuvColorLookup[index].pixelFormat; + bitsPerPixel = qt_yuvColorLookup[index].bitsPerPixel; + } + } else if (qstrcmp(gst_structure_get_name(structure), "video/x-raw-rgb") == 0) { + int depth = 0; + int endianness = 0; + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + + gst_structure_get_int(structure, "bpp", &bitsPerPixel); + gst_structure_get_int(structure, "depth", &depth); + gst_structure_get_int(structure, "endianness", &endianness); + gst_structure_get_int(structure, "red_mask", &red); + gst_structure_get_int(structure, "green_mask", &green); + gst_structure_get_int(structure, "blue_mask", &blue); + gst_structure_get_int(structure, "alpha_mask", &alpha); + + int index = indexOfRgbColor(bitsPerPixel, depth, endianness, red, green, blue, alpha); + + if (index != -1) + pixelFormat = qt_rgbColorLookup[index].pixelFormat; + } + + if (pixelFormat != QVideoFrame::Format_Invalid) { + QVideoSurfaceFormat format(size, pixelFormat); + + QPair<int, int> rate; + gst_structure_get_fraction(structure, "framerate", &rate.first, &rate.second); + + if (rate.second) + format.setFrameRate(qreal(rate.first)/rate.second); + + gint aspectNum = 0; + gint aspectDenum = 0; + if (gst_structure_get_fraction( + structure, "pixel-aspect-ratio", &aspectNum, &aspectDenum)) { + if (aspectDenum > 0) + format.setPixelAspectRatio(aspectNum, aspectDenum); + } + + if (bytesPerLine) + *bytesPerLine = ((size.width() * bitsPerPixel / 8) + 3) & ~3; + + return format; + } + + return QVideoSurfaceFormat(); +} + + +GstFlowReturn QVideoSurfaceGstSink::buffer_alloc( + GstBaseSink *base, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer) +{ + VO_SINK(base); + + Q_UNUSED(offset); + Q_UNUSED(size); + + if (!buffer) + return GST_FLOW_ERROR; + + *buffer = NULL; + + if (!sink->delegate->pool()) + return GST_FLOW_OK; + + QMutexLocker poolLock(sink->delegate->poolMutex()); + QAbstractGstBufferPool *pool = sink->delegate->pool(); + + if (!pool) + return GST_FLOW_OK; + + if (sink->lastRequestedCaps && gst_caps_is_equal(sink->lastRequestedCaps, caps)) { + //qDebug() << "reusing last caps"; + *buffer = GST_BUFFER(pool->takeBuffer(*sink->lastSurfaceFormat, sink->lastBufferCaps)); + return GST_FLOW_OK; + } + + if (sink->delegate->supportedPixelFormats(pool->handleType()).isEmpty()) { + //qDebug() << "sink doesn't support native pool buffers, skip buffers allocation"; + return GST_FLOW_OK; + } + + GstCaps *intersection = gst_caps_intersect(get_caps(GST_BASE_SINK(sink)), caps); + + if (gst_caps_is_empty (intersection)) { + gst_caps_unref(intersection); + return GST_FLOW_NOT_NEGOTIATED; + } + + poolLock.unlock(); + + if (sink->delegate->isActive()) { + //if format was changed, restart the surface + QVideoSurfaceFormat format = formatForCaps(intersection); + QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat(); + + if (format.pixelFormat() != surfaceFormat.pixelFormat() || + format.frameSize() != surfaceFormat.frameSize()) { +#ifdef DEBUG_VIDEO_SURFACE_SINK + qDebug() << "new format requested, restart video surface"; +#endif + sink->delegate->stop(); + } + } + + if (!sink->delegate->isActive()) { + int bytesPerLine = 0; + QVideoSurfaceFormat format = formatForCaps(intersection, &bytesPerLine); + + if (!sink->delegate->start(format, bytesPerLine)) { + qWarning() << "failed to start video surface"; + return GST_FLOW_NOT_NEGOTIATED; + } + } + + poolLock.relock(); + pool = sink->delegate->pool(); + + QVideoSurfaceFormat surfaceFormat = sink->delegate->surfaceFormat(); + + if (!pool->isFormatSupported(surfaceFormat)) { + //qDebug() << "sink doesn't support native pool format, skip custom buffers allocation"; + return GST_FLOW_OK; + } + + if (sink->lastRequestedCaps) + gst_caps_unref(sink->lastRequestedCaps); + sink->lastRequestedCaps = caps; + gst_caps_ref(sink->lastRequestedCaps); + + if (sink->lastBufferCaps) + gst_caps_unref(sink->lastBufferCaps); + sink->lastBufferCaps = intersection; + gst_caps_ref(sink->lastBufferCaps); + + *sink->lastSurfaceFormat = surfaceFormat; + + *buffer = GST_BUFFER(pool->takeBuffer(surfaceFormat, intersection)); + + return GST_FLOW_OK; +} + +gboolean QVideoSurfaceGstSink::start(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::stop(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::unlock(GstBaseSink *base) +{ + Q_UNUSED(base); + + return TRUE; +} + +gboolean QVideoSurfaceGstSink::event(GstBaseSink *base, GstEvent *event) +{ + Q_UNUSED(base); + Q_UNUSED(event); + + return TRUE; +} + +GstFlowReturn QVideoSurfaceGstSink::preroll(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + return sink->delegate->render(buffer); +} + +GstFlowReturn QVideoSurfaceGstSink::render(GstBaseSink *base, GstBuffer *buffer) +{ + VO_SINK(base); + return sink->delegate->render(buffer); +} + diff --git a/src/plugins/gstreamer/qvideosurfacegstsink.h b/src/plugins/gstreamer/qvideosurfacegstsink.h new file mode 100644 index 000000000..531a76e0f --- /dev/null +++ b/src/plugins/gstreamer/qvideosurfacegstsink.h @@ -0,0 +1,162 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef VIDEOSURFACEGSTSINK_H +#define VIDEOSURFACEGSTSINK_H + +#include <gst/video/gstvideosink.h> + +#include <QtCore/qlist.h> +#include <QtCore/qmutex.h> +#include <QtCore/qqueue.h> +#include <QtCore/qpointer.h> +#include <QtCore/qwaitcondition.h> +#include <qvideosurfaceformat.h> +#include <qvideoframe.h> +#include <qabstractvideobuffer.h> + +#include "qabstractgstbufferpool.h" + +QT_BEGIN_NAMESPACE +class QAbstractVideoSurface; +QT_END_NAMESPACE + +#if defined(Q_WS_X11) && !defined(QT_NO_XVIDEO) +class QGstXvImageBuffer; +class QGstXvImageBufferPool; +#endif + +class QVideoSurfaceGstDelegate : public QObject +{ + Q_OBJECT +public: + QVideoSurfaceGstDelegate(QAbstractVideoSurface *surface); + ~QVideoSurfaceGstDelegate(); + + QList<QVideoFrame::PixelFormat> supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const; + + QVideoSurfaceFormat surfaceFormat() const; + + bool start(const QVideoSurfaceFormat &format, int bytesPerLine); + void stop(); + + bool isActive(); + + QAbstractGstBufferPool *pool() { return m_pool; } + QMutex *poolMutex() { return &m_poolMutex; } + + GstFlowReturn render(GstBuffer *buffer); + +private slots: + void queuedStart(); + void queuedStop(); + void queuedRender(); + + void updateSupportedFormats(); + +private: + QPointer<QAbstractVideoSurface> m_surface; + QList<QVideoFrame::PixelFormat> m_supportedPixelFormats; + //pixel formats of buffers pool native type + QList<QVideoFrame::PixelFormat> m_supportedPoolPixelFormats; + QAbstractGstBufferPool *m_pool; + QList<QAbstractGstBufferPool *> m_pools; + QMutex m_poolMutex; + QMutex m_mutex; + QWaitCondition m_setupCondition; + QWaitCondition m_renderCondition; + QVideoSurfaceFormat m_format; + QVideoFrame m_frame; + GstFlowReturn m_renderReturn; + int m_bytesPerLine; + bool m_started; +}; + +class QVideoSurfaceGstSink +{ +public: + GstVideoSink parent; + + static QVideoSurfaceGstSink *createSink(QAbstractVideoSurface *surface); + static QVideoSurfaceFormat formatForCaps(GstCaps *caps, int *bytesPerLine = 0); + +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); + static gboolean set_caps(GstBaseSink *sink, GstCaps *caps); + + static GstFlowReturn buffer_alloc( + GstBaseSink *sink, guint64 offset, guint size, GstCaps *caps, GstBuffer **buffer); + + static gboolean start(GstBaseSink *sink); + static gboolean stop(GstBaseSink *sink); + + static gboolean unlock(GstBaseSink *sink); + + static gboolean event(GstBaseSink *sink, GstEvent *event); + static GstFlowReturn preroll(GstBaseSink *sink, GstBuffer *buffer); + static GstFlowReturn render(GstBaseSink *sink, GstBuffer *buffer); + +private: + QVideoSurfaceGstDelegate *delegate; + + GstCaps *lastRequestedCaps; + GstCaps *lastBufferCaps; + QVideoSurfaceFormat *lastSurfaceFormat; +}; + + +class QVideoSurfaceGstSinkClass +{ +public: + GstVideoSinkClass parent_class; +}; + +#endif diff --git a/src/plugins/gstreamer/qx11videosurface.cpp b/src/plugins/gstreamer/qx11videosurface.cpp new file mode 100644 index 000000000..ea2a1a3a2 --- /dev/null +++ b/src/plugins/gstreamer/qx11videosurface.cpp @@ -0,0 +1,535 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qvariant.h> +#include <QtCore/qdebug.h> +#include <QtGui/qx11info_x11.h> +#include <qvideosurfaceformat.h> + +#ifndef QT_NO_XVIDEO + +#include "qx11videosurface.h" + +Q_DECLARE_METATYPE(XvImage*); + +struct XvFormatRgb +{ + QVideoFrame::PixelFormat pixelFormat; + int bits_per_pixel; + int format; + int num_planes; + + int depth; + unsigned int red_mask; + unsigned int green_mask; + unsigned int blue_mask; + +}; + +bool operator ==(const XvImageFormatValues &format, const XvFormatRgb &rgb) +{ + return format.type == XvRGB + && format.bits_per_pixel == rgb.bits_per_pixel + && format.format == rgb.format + && format.num_planes == rgb.num_planes + && format.depth == rgb.depth + && format.red_mask == rgb.red_mask + && format.blue_mask == rgb.blue_mask; +} + +static const XvFormatRgb qt_xvRgbLookup[] = +{ + { QVideoFrame::Format_ARGB32, 32, XvPacked, 1, 32, 0x00FF0000, 0x0000FF00, 0x000000FF }, + { QVideoFrame::Format_RGB32 , 32, XvPacked, 1, 24, 0x00FF0000, 0x0000FF00, 0x000000FF }, + { QVideoFrame::Format_RGB24 , 24, XvPacked, 1, 24, 0x00FF0000, 0x0000FF00, 0x000000FF }, + { QVideoFrame::Format_RGB565, 16, XvPacked, 1, 16, 0x0000F800, 0x000007E0, 0x0000001F }, + { QVideoFrame::Format_BGRA32, 32, XvPacked, 1, 32, 0xFF000000, 0x00FF0000, 0x0000FF00 }, + { QVideoFrame::Format_BGR32 , 32, XvPacked, 1, 24, 0x00FF0000, 0x0000FF00, 0x000000FF }, + { QVideoFrame::Format_BGR24 , 24, XvPacked, 1, 24, 0x00FF0000, 0x0000FF00, 0x000000FF }, + { QVideoFrame::Format_BGR565, 16, XvPacked, 1, 16, 0x0000F800, 0x000007E0, 0x0000001F } +}; + +struct XvFormatYuv +{ + QVideoFrame::PixelFormat pixelFormat; + int bits_per_pixel; + int format; + int num_planes; + + unsigned int y_sample_bits; + unsigned int u_sample_bits; + unsigned int v_sample_bits; + unsigned int horz_y_period; + unsigned int horz_u_period; + unsigned int horz_v_period; + unsigned int vert_y_period; + unsigned int vert_u_period; + unsigned int vert_v_period; + char component_order[32]; +}; + +bool operator ==(const XvImageFormatValues &format, const XvFormatYuv &yuv) +{ + return format.type == XvYUV + && format.bits_per_pixel == yuv.bits_per_pixel + && format.format == yuv.format + && format.num_planes == yuv.num_planes + && format.y_sample_bits == yuv.y_sample_bits + && format.u_sample_bits == yuv.u_sample_bits + && format.v_sample_bits == yuv.v_sample_bits + && format.horz_y_period == yuv.horz_y_period + && format.horz_u_period == yuv.horz_u_period + && format.horz_v_period == yuv.horz_v_period + && format.horz_y_period == yuv.vert_y_period + && format.vert_u_period == yuv.vert_u_period + && format.vert_v_period == yuv.vert_v_period + && qstrncmp(format.component_order, yuv.component_order, 32) == 0; +} + +static const XvFormatYuv qt_xvYuvLookup[] = +{ + { QVideoFrame::Format_YUV444 , 24, XvPacked, 1, 8, 8, 8, 1, 1, 1, 1, 1, 1, "YUV" }, + { QVideoFrame::Format_YUV420P, 12, XvPlanar, 3, 8, 8, 8, 1, 2, 2, 1, 2, 2, "YUV" }, + { QVideoFrame::Format_YV12 , 12, XvPlanar, 3, 8, 8, 8, 1, 2, 2, 1, 2, 2, "YVU" }, + { QVideoFrame::Format_UYVY , 16, XvPacked, 1, 8, 8, 8, 1, 2, 2, 1, 1, 1, "UYVY" }, + { QVideoFrame::Format_YUYV , 16, XvPacked, 1, 8, 8, 8, 1, 2, 2, 1, 1, 1, "YUY2" }, + { QVideoFrame::Format_YUYV , 16, XvPacked, 1, 8, 8, 8, 1, 2, 2, 1, 1, 1, "YUYV" }, + { QVideoFrame::Format_NV12 , 12, XvPlanar, 2, 8, 8, 8, 1, 2, 2, 1, 2, 2, "YUV" }, + { QVideoFrame::Format_NV12 , 12, XvPlanar, 2, 8, 8, 8, 1, 2, 2, 1, 2, 2, "YVU" }, + { QVideoFrame::Format_Y8 , 8 , XvPlanar, 1, 8, 0, 0, 1, 0, 0, 1, 0, 0, "Y" } +}; + +QX11VideoSurface::QX11VideoSurface(QObject *parent) + : QAbstractVideoSurface(parent) + , m_winId(0) + , m_portId(0) + , m_gc(0) + , m_image(0) +{ +} + +QX11VideoSurface::~QX11VideoSurface() +{ + if (m_gc) + XFreeGC(QX11Info::display(), m_gc); + + if (m_portId != 0) + XvUngrabPort(QX11Info::display(), m_portId, 0); +} + +WId QX11VideoSurface::winId() const +{ + return m_winId; +} + +void QX11VideoSurface::setWinId(WId id) +{ + //qDebug() << "setWinID:" << id; + + if (id == m_winId) + return; + + if (m_image) + XFree(m_image); + + if (m_gc) { + XFreeGC(QX11Info::display(), m_gc); + m_gc = 0; + } + + if (m_portId != 0) + XvUngrabPort(QX11Info::display(), m_portId, 0); + + m_supportedPixelFormats.clear(); + m_formatIds.clear(); + + m_winId = id; + + if (m_winId && findPort()) { + querySupportedFormats(); + + m_gc = XCreateGC(QX11Info::display(), m_winId, 0, 0); + + if (m_image) { + m_image = 0; + + if (!start(surfaceFormat())) { + QAbstractVideoSurface::stop(); + qWarning() << "Failed to start video surface with format" << surfaceFormat(); + } + } + } else { + qWarning() << "Failed to find XVideo port"; + if (m_image) { + m_image = 0; + + QAbstractVideoSurface::stop(); + } + } + + emit supportedFormatsChanged(); +} + +QRect QX11VideoSurface::displayRect() const +{ + return m_displayRect; +} + +void QX11VideoSurface::setDisplayRect(const QRect &rect) +{ + m_displayRect = rect; +} + +QRect QX11VideoSurface::viewport() const +{ + return m_viewport; +} + +void QX11VideoSurface::setViewport(const QRect &rect) +{ + m_viewport = rect; +} + +int QX11VideoSurface::brightness() const +{ + return getAttribute("XV_BRIGHTNESS", m_brightnessRange.first, m_brightnessRange.second); +} + +void QX11VideoSurface::setBrightness(int brightness) +{ + setAttribute("XV_BRIGHTNESS", brightness, m_brightnessRange.first, m_brightnessRange.second); +} + +int QX11VideoSurface::contrast() const +{ + return getAttribute("XV_CONTRAST", m_contrastRange.first, m_contrastRange.second); +} + +void QX11VideoSurface::setContrast(int contrast) +{ + setAttribute("XV_CONTRAST", contrast, m_contrastRange.first, m_contrastRange.second); +} + +int QX11VideoSurface::hue() const +{ + return getAttribute("XV_HUE", m_hueRange.first, m_hueRange.second); +} + +void QX11VideoSurface::setHue(int hue) +{ + setAttribute("XV_HUE", hue, m_hueRange.first, m_hueRange.second); +} + +int QX11VideoSurface::saturation() const +{ + return getAttribute("XV_SATURATION", m_saturationRange.first, m_saturationRange.second); +} + +void QX11VideoSurface::setSaturation(int saturation) +{ + setAttribute("XV_SATURATION", saturation, m_saturationRange.first, m_saturationRange.second); +} + +int QX11VideoSurface::getAttribute(const char *attribute, int minimum, int maximum) const +{ + if (m_portId != 0) { + Display *display = QX11Info::display(); + + Atom atom = XInternAtom(display, attribute, True); + + int value = 0; + + XvGetPortAttribute(display, m_portId, atom, &value); + + return redistribute(value, minimum, maximum, -100, 100); + } else { + return 0; + } +} + +void QX11VideoSurface::setAttribute(const char *attribute, int value, int minimum, int maximum) +{ + if (m_portId != 0) { + Display *display = QX11Info::display(); + + Atom atom = XInternAtom(display, attribute, True); + + XvSetPortAttribute( + display, m_portId, atom, redistribute(value, -100, 100, minimum, maximum)); + } +} + +int QX11VideoSurface::redistribute( + int value, int fromLower, int fromUpper, int toLower, int toUpper) +{ + return fromUpper != fromLower + ? ((value - fromLower) * (toUpper - toLower) / (fromUpper - fromLower)) + toLower + : 0; +} + +QList<QVideoFrame::PixelFormat> QX11VideoSurface::supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType) const +{ + return handleType == QAbstractVideoBuffer::NoHandle || handleType == QAbstractVideoBuffer::XvShmImageHandle + ? m_supportedPixelFormats + : QList<QVideoFrame::PixelFormat>(); +} + +bool QX11VideoSurface::start(const QVideoSurfaceFormat &format) +{ + if (m_image) + XFree(m_image); + + int xvFormatId = 0; + for (int i = 0; i < m_supportedPixelFormats.count(); ++i) { + if (m_supportedPixelFormats.at(i) == format.pixelFormat()) { + xvFormatId = m_formatIds.at(i); + break; + } + } + + if (xvFormatId == 0) { + setError(UnsupportedFormatError); + } else { + XvImage *image = XvCreateImage( + QX11Info::display(), + m_portId, + xvFormatId, + 0, + format.frameWidth(), + format.frameHeight()); + + if (!image) { + setError(ResourceError); + } else { + m_viewport = format.viewport(); + m_image = image; + + QVideoSurfaceFormat newFormat = format; + newFormat.setProperty("portId", QVariant(quint64(m_portId))); + newFormat.setProperty("xvFormatId", xvFormatId); + newFormat.setProperty("dataSize", image->data_size); + + return QAbstractVideoSurface::start(newFormat); + } + } + + if (m_image) { + m_image = 0; + + QAbstractVideoSurface::stop(); + } + + return false; +} + +void QX11VideoSurface::stop() +{ + if (m_image) { + XFree(m_image); + m_image = 0; + + QAbstractVideoSurface::stop(); + } +} + +bool QX11VideoSurface::present(const QVideoFrame &frame) +{ + if (!m_image) { + setError(StoppedError); + return false; + } else if (m_image->width != frame.width() || m_image->height != frame.height()) { + setError(IncorrectFormatError); + return false; + } else { + QVideoFrame frameCopy(frame); + + if (!frameCopy.map(QAbstractVideoBuffer::ReadOnly)) { + setError(IncorrectFormatError); + return false; + } else { + bool presented = false; + + if (frame.handleType() != QAbstractVideoBuffer::XvShmImageHandle && + m_image->data_size > frame.mappedBytes()) { + qWarning("Insufficient frame buffer size"); + setError(IncorrectFormatError); + } else if (frame.handleType() != QAbstractVideoBuffer::XvShmImageHandle && + m_image->num_planes > 0 && + m_image->pitches[0] != frame.bytesPerLine()) { + qWarning("Incompatible frame pitches"); + setError(IncorrectFormatError); + } else { + if (frame.handleType() != QAbstractVideoBuffer::XvShmImageHandle) { + m_image->data = reinterpret_cast<char *>(frameCopy.bits()); + + //qDebug() << "copy frame"; + XvPutImage( + QX11Info::display(), + m_portId, + m_winId, + m_gc, + m_image, + m_viewport.x(), + m_viewport.y(), + m_viewport.width(), + m_viewport.height(), + m_displayRect.x(), + m_displayRect.y(), + m_displayRect.width(), + m_displayRect.height()); + + m_image->data = 0; + } else { + XvImage *img = frame.handle().value<XvImage*>(); + + //qDebug() << "render directly"; + if (img) + XvShmPutImage( + QX11Info::display(), + m_portId, + m_winId, + m_gc, + img, + m_viewport.x(), + m_viewport.y(), + m_viewport.width(), + m_viewport.height(), + m_displayRect.x(), + m_displayRect.y(), + m_displayRect.width(), + m_displayRect.height(), + false); + } + + presented = true; + } + + frameCopy.unmap(); + + return presented; + } + } +} + +bool QX11VideoSurface::findPort() +{ + unsigned int count = 0; + XvAdaptorInfo *adaptors = 0; + bool portFound = false; + + if (XvQueryAdaptors(QX11Info::display(), m_winId, &count, &adaptors) == Success) { +#ifdef Q_WS_MAEMO_5 + //the overlay xvideo adapter fails to switch winId, + //prefer the "SGX Textured Video" adapter instead + for (int i = count-1; i >= 0 && !portFound; --i) { +#else + for (unsigned int i = 0; i < count && !portFound; ++i) { +#endif + if (adaptors[i].type & XvImageMask) { + m_portId = adaptors[i].base_id; + + for (unsigned int j = 0; j < adaptors[i].num_ports && !portFound; ++j, ++m_portId) + portFound = XvGrabPort(QX11Info::display(), m_portId, 0) == Success; + } + } + XvFreeAdaptorInfo(adaptors); + } + + return portFound; +} + +void QX11VideoSurface::querySupportedFormats() +{ + int count = 0; + if (XvImageFormatValues *imageFormats = XvListImageFormats( + QX11Info::display(), m_portId, &count)) { + const int rgbCount = sizeof(qt_xvRgbLookup) / sizeof(XvFormatRgb); + const int yuvCount = sizeof(qt_xvYuvLookup) / sizeof(XvFormatYuv); + + for (int i = 0; i < count; ++i) { + switch (imageFormats[i].type) { + case XvRGB: + for (int j = 0; j < rgbCount; ++j) { + if (imageFormats[i] == qt_xvRgbLookup[j]) { + m_supportedPixelFormats.append(qt_xvRgbLookup[j].pixelFormat); + m_formatIds.append(imageFormats[i].id); + break; + } + } + break; + case XvYUV: + for (int j = 0; j < yuvCount; ++j) { + if (imageFormats[i] == qt_xvYuvLookup[j]) { + m_supportedPixelFormats.append(qt_xvYuvLookup[j].pixelFormat); + m_formatIds.append(imageFormats[i].id); + break; + } + } + break; + } + } + XFree(imageFormats); + } + + m_brightnessRange = qMakePair(0, 0); + m_contrastRange = qMakePair(0, 0); + m_hueRange = qMakePair(0, 0); + m_saturationRange = qMakePair(0, 0); + + if (XvAttribute *attributes = XvQueryPortAttributes(QX11Info::display(), m_portId, &count)) { + for (int i = 0; i < count; ++i) { + if (qstrcmp(attributes[i].name, "XV_BRIGHTNESS") == 0) + m_brightnessRange = qMakePair(attributes[i].min_value, attributes[i].max_value); + else if (qstrcmp(attributes[i].name, "XV_CONTRAST") == 0) + m_contrastRange = qMakePair(attributes[i].min_value, attributes[i].max_value); + else if (qstrcmp(attributes[i].name, "XV_HUE") == 0) + m_hueRange = qMakePair(attributes[i].min_value, attributes[i].max_value); + else if (qstrcmp(attributes[i].name, "XV_SATURATION") == 0) + m_saturationRange = qMakePair(attributes[i].min_value, attributes[i].max_value); + } + + XFree(attributes); + } +} + +#endif //QT_NO_XVIDEO + diff --git a/src/plugins/gstreamer/qx11videosurface.h b/src/plugins/gstreamer/qx11videosurface.h new file mode 100644 index 000000000..7bebe2f67 --- /dev/null +++ b/src/plugins/gstreamer/qx11videosurface.h @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QX11VIDEOSURFACE_H +#define QX11VIDEOSURFACE_H + +#include <QtGui/qwidget.h> +#include <qabstractvideosurface.h> + +#ifndef QT_NO_XVIDEO + +#include <X11/Xlib.h> +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> + +QT_USE_NAMESPACE + +class QX11VideoSurface : public QAbstractVideoSurface +{ + Q_OBJECT +public: + QX11VideoSurface(QObject *parent = 0); + ~QX11VideoSurface(); + + WId winId() const; + void setWinId(WId id); + + QRect displayRect() const; + void setDisplayRect(const QRect &rect); + + QRect viewport() const; + void setViewport(const QRect &rect); + + int brightness() const; + void setBrightness(int brightness); + + int contrast() const; + void setContrast(int contrast); + + int hue() const; + void setHue(int hue); + + int saturation() const; + void setSaturation(int saturation); + + QList<QVideoFrame::PixelFormat> supportedPixelFormats( + QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const; + + bool start(const QVideoSurfaceFormat &format); + void stop(); + + bool present(const QVideoFrame &frame); + +private: + WId m_winId; + XvPortID m_portId; + GC m_gc; + XvImage *m_image; + QList<QVideoFrame::PixelFormat> m_supportedPixelFormats; + QVector<int> m_formatIds; + QRect m_viewport; + QRect m_displayRect; + QPair<int, int> m_brightnessRange; + QPair<int, int> m_contrastRange; + QPair<int, int> m_hueRange; + QPair<int, int> m_saturationRange; + + bool findPort(); + void querySupportedFormats(); + + int getAttribute(const char *attribute, int minimum, int maximum) const; + void setAttribute(const char *attribute, int value, int minimum, int maximum); + + static int redistribute(int value, int fromLower, int fromUpper, int toLower, int toUpper); +}; + +#endif //QT_NO_XVIDEO + +#endif |