summaryrefslogtreecommitdiffstats
path: root/src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp')
-rw-r--r--src/plugins/gstreamer/mediacapture/qgstreamercapturesession.cpp1051
1 files changed, 1051 insertions, 0 deletions
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);
+ }
+}