diff options
Diffstat (limited to 'src/plugins/gstreamer')
7 files changed, 572 insertions, 822 deletions
diff --git a/src/plugins/gstreamer/audiodecoder/audiodecoder.pro b/src/plugins/gstreamer/audiodecoder/audiodecoder.pro index 4e816e920..7e61beccc 100644 --- a/src/plugins/gstreamer/audiodecoder/audiodecoder.pro +++ b/src/plugins/gstreamer/audiodecoder/audiodecoder.pro @@ -5,15 +5,13 @@ include(../common.pri) INCLUDEPATH += $$PWD HEADERS += \ - $$PWD/qgstreameraudiodecodercontrol.h \ $$PWD/qgstreameraudiodecoderservice.h \ - $$PWD/qgstreameraudiodecodersession.h \ + $$PWD/qgstreameraudiodecodercontrol.h \ $$PWD/qgstreameraudiodecoderserviceplugin.h SOURCES += \ - $$PWD/qgstreameraudiodecodercontrol.cpp \ $$PWD/qgstreameraudiodecoderservice.cpp \ - $$PWD/qgstreameraudiodecodersession.cpp \ + $$PWD/qgstreameraudiodecodercontrol.cpp \ $$PWD/qgstreameraudiodecoderserviceplugin.cpp OTHER_FILES += \ diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.cpp b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.cpp index e9a7a5332..cda9e250b 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.cpp +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Toolkit. @@ -36,104 +36,564 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ +//#define DEBUG_DECODER #include "qgstreameraudiodecodercontrol.h" -#include "qgstreameraudiodecodersession.h" +#include <private/qgstreamerbushelper_p.h> +#include <private/qgstutils_p.h> + +#include <gst/gstvalue.h> +#include <gst/base/gstbasesrc.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 <QtCore/qsocketnotifier.h> +#include <QtCore/qstandardpaths.h> #include <QtCore/qurl.h> -#include <QtCore/qdebug.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> +#define MAX_BUFFERS_IN_QUEUE 4 QT_BEGIN_NAMESPACE -QGstreamerAudioDecoderControl::QGstreamerAudioDecoderControl(QGstreamerAudioDecoderSession *session, QObject *parent) - : QAudioDecoderControl(parent) - , m_session(session) +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; + +QGstreamerAudioDecoderControl::QGstreamerAudioDecoderControl(QObject *parent) + : QAudioDecoderControl(parent), + m_state(QAudioDecoder::StoppedState), + m_pendingState(QAudioDecoder::StoppedState), + m_busHelper(0), + m_bus(0), + m_playbin(0), + m_outputBin(0), + m_audioConvert(0), + m_appSink(0), +#if QT_CONFIG(gstreamer_app) + m_appSrc(0), +#endif + mDevice(0), + m_buffersAvailable(0), + m_position(-1), + m_duration(-1), + m_durationQueries(0) { - connect(m_session, SIGNAL(bufferAvailableChanged(bool)), this, SIGNAL(bufferAvailableChanged(bool))); - connect(m_session, SIGNAL(bufferReady()), this, SIGNAL(bufferReady())); - connect(m_session, SIGNAL(error(int,QString)), this, SIGNAL(error(int,QString))); - connect(m_session, SIGNAL(formatChanged(QAudioFormat)), this, SIGNAL(formatChanged(QAudioFormat))); - connect(m_session, SIGNAL(sourceChanged()), this, SIGNAL(sourceChanged())); - connect(m_session, SIGNAL(stateChanged(QAudioDecoder::State)), this, SIGNAL(stateChanged(QAudioDecoder::State))); - connect(m_session, SIGNAL(finished()), this, SIGNAL(finished())); - connect(m_session, SIGNAL(positionChanged(qint64)), this, SIGNAL(positionChanged(qint64))); - connect(m_session, SIGNAL(durationChanged(qint64)), this, SIGNAL(durationChanged(qint64))); + // Create pipeline here + m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, NULL); + + if (m_playbin != 0) { + // Sort out messages + m_bus = gst_element_get_bus(m_playbin); + m_busHelper = new QGstreamerBusHelper(m_bus, this); + m_busHelper->installMessageFilter(this); + + // Set the rest of the pipeline up + setAudioFlags(true); + + m_audioConvert = gst_element_factory_make("audioconvert", NULL); + + m_outputBin = gst_bin_new("audio-output-bin"); + gst_bin_add(GST_BIN(m_outputBin), m_audioConvert); + + // add ghostpad + GstPad *pad = gst_element_get_static_pad(m_audioConvert, "sink"); + Q_ASSERT(pad); + gst_element_add_pad(GST_ELEMENT(m_outputBin), gst_ghost_pad_new("sink", pad)); + gst_object_unref(GST_OBJECT(pad)); + + g_object_set(G_OBJECT(m_playbin), "audio-sink", m_outputBin, NULL); +#if QT_CONFIG(gstreamer_app) + g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderControl::configureAppSrcElement, (gpointer)this); +#endif + + // Set volume to 100% + gdouble volume = 1.0; + g_object_set(G_OBJECT(m_playbin), "volume", volume, NULL); + } } QGstreamerAudioDecoderControl::~QGstreamerAudioDecoderControl() { + if (m_playbin) { + stop(); + + delete m_busHelper; +#if QT_CONFIG(gstreamer_app) + delete m_appSrc; +#endif + gst_object_unref(GST_OBJECT(m_bus)); + gst_object_unref(GST_OBJECT(m_playbin)); + } +} + +#if QT_CONFIG(gstreamer_app) +void QGstreamerAudioDecoderControl::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerAudioDecoderControl* self) +{ + Q_UNUSED(object); + Q_UNUSED(pspec); + + // In case we switch from appsrc to not + if (!self->appsrc()) + return; + + GstElement *appsrc; + g_object_get(orig, "source", &appsrc, NULL); + if (!self->appsrc()->setup(appsrc)) + qWarning()<<"Could not setup appsrc element"; + + g_object_unref(G_OBJECT(appsrc)); } +#endif -QAudioDecoder::State QGstreamerAudioDecoderControl::state() const +bool QGstreamerAudioDecoderControl::processBusMessage(const QGstreamerMessage &message) { - return m_session->pendingState(); + GstMessage* gm = message.rawMessage(); + if (gm) { + if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) { + updateDuration(); + } else 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_DECODER + 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]) << "internal" << m_state; +#endif + + QAudioDecoder::State prevState = m_state; + + switch (newState) { + case GST_STATE_VOID_PENDING: + case GST_STATE_NULL: + m_state = QAudioDecoder::StoppedState; + break; + case GST_STATE_READY: + m_state = QAudioDecoder::StoppedState; + break; + case GST_STATE_PLAYING: + m_state = QAudioDecoder::DecodingState; + break; + case GST_STATE_PAUSED: + m_state = QAudioDecoder::DecodingState; + + //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(); + break; + } + + if (prevState != m_state) + emit stateChanged(m_state); + } + break; + + case GST_MESSAGE_EOS: + m_pendingState = m_state = QAudioDecoder::StoppedState; + emit finished(); + emit stateChanged(m_state); + 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(QAudioDecoder::FormatError, tr("Cannot play stream of type: <unknown>")); + else + processInvalidMedia(QAudioDecoder::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; +#ifdef DEBUG_DECODER + case GST_MESSAGE_INFO: + { + GError *err; + gchar *debug; + gst_message_parse_info (gm, &err, &debug); + qDebug() << "Info:" << QString::fromUtf8(err->message); + g_error_free (err); + g_free (debug); + } + break; +#endif + default: + break; + } + } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { + GError *err; + gchar *debug; + gst_message_parse_error(gm, &err, &debug); + QAudioDecoder::Error qerror = QAudioDecoder::ResourceError; + if (err->domain == GST_STREAM_ERROR) { + switch (err->code) { + case GST_STREAM_ERROR_DECRYPT: + case GST_STREAM_ERROR_DECRYPT_NOKEY: + qerror = QAudioDecoder::AccessDeniedError; + break; + case GST_STREAM_ERROR_FORMAT: + case GST_STREAM_ERROR_DEMUX: + case GST_STREAM_ERROR_DECODE: + case GST_STREAM_ERROR_WRONG_TYPE: + case GST_STREAM_ERROR_TYPE_NOT_FOUND: + case GST_STREAM_ERROR_CODEC_NOT_FOUND: + qerror = QAudioDecoder::FormatError; + break; + default: + break; + } + } else if (err->domain == GST_CORE_ERROR) { + switch (err->code) { + case GST_CORE_ERROR_MISSING_PLUGIN: + qerror = QAudioDecoder::FormatError; + break; + default: + break; + } + } + + processInvalidMedia(qerror, QString::fromUtf8(err->message)); + g_error_free(err); + g_free(debug); + } + } + + return false; } QString QGstreamerAudioDecoderControl::sourceFilename() const { - return m_session->sourceFilename(); + return mSource; } void QGstreamerAudioDecoderControl::setSourceFilename(const QString &fileName) { - m_session->setSourceFilename(fileName); + stop(); + mDevice = 0; +#if QT_CONFIG(gstreamer_app) + if (m_appSrc) + m_appSrc->deleteLater(); + m_appSrc = 0; +#endif + + bool isSignalRequired = (mSource != fileName); + mSource = fileName; + if (isSignalRequired) + emit sourceChanged(); } -QIODevice* QGstreamerAudioDecoderControl::sourceDevice() const +QIODevice *QGstreamerAudioDecoderControl::sourceDevice() const { - return m_session->sourceDevice(); + return mDevice; } void QGstreamerAudioDecoderControl::setSourceDevice(QIODevice *device) { - m_session->setSourceDevice(device); + stop(); + mSource.clear(); + bool isSignalRequired = (mDevice != device); + mDevice = device; + if (isSignalRequired) + emit sourceChanged(); } void QGstreamerAudioDecoderControl::start() { - m_session->start(); + if (!m_playbin) { + processInvalidMedia(QAudioDecoder::ResourceError, "Playbin element is not valid"); + return; + } + + addAppSink(); + + if (!mSource.isEmpty()) { + g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL); + } else if (mDevice) { +#if QT_CONFIG(gstreamer_app) + // make sure we can read from device + if (!mDevice->isOpen() || !mDevice->isReadable()) { + processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device"); + return; + } + + if (!m_appSrc) + m_appSrc = new QGstAppSrc(this); + m_appSrc->setStream(mDevice); + + g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL); +#endif + } else { + return; + } + + // Set audio format + if (m_appSink) { + if (mFormat.isValid()) { + setAudioFlags(false); + GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat); + gst_app_sink_set_caps(m_appSink, caps); + gst_caps_unref(caps); + } else { + // We want whatever the native audio format is + setAudioFlags(true); + gst_app_sink_set_caps(m_appSink, NULL); + } + } + + m_pendingState = QAudioDecoder::DecodingState; + if (gst_element_set_state(m_playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { + qWarning() << "GStreamer; Unable to start decoding process"; + m_pendingState = m_state = QAudioDecoder::StoppedState; + + emit stateChanged(m_state); + } } void QGstreamerAudioDecoderControl::stop() { - m_session->stop(); + if (m_playbin) { + gst_element_set_state(m_playbin, GST_STATE_NULL); + removeAppSink(); + + QAudioDecoder::State oldState = m_state; + m_pendingState = m_state = QAudioDecoder::StoppedState; + + // GStreamer thread is stopped. Can safely access m_buffersAvailable + if (m_buffersAvailable != 0) { + m_buffersAvailable = 0; + emit bufferAvailableChanged(false); + } + + if (m_position != -1) { + m_position = -1; + emit positionChanged(m_position); + } + + if (m_duration != -1) { + m_duration = -1; + emit durationChanged(m_duration); + } + + if (oldState != m_state) + emit stateChanged(m_state); + } } QAudioFormat QGstreamerAudioDecoderControl::audioFormat() const { - return m_session->audioFormat(); + return mFormat; } void QGstreamerAudioDecoderControl::setAudioFormat(const QAudioFormat &format) { - m_session->setAudioFormat(format); + if (mFormat != format) { + mFormat = format; + emit formatChanged(mFormat); + } } QAudioBuffer QGstreamerAudioDecoderControl::read() { - return m_session->read(); + QAudioBuffer audioBuffer; + + int buffersAvailable; + { + QMutexLocker locker(&m_buffersMutex); + buffersAvailable = m_buffersAvailable; + + // need to decrement before pulling a buffer + // to make sure assert in QGstreamerAudioDecoderControl::new_buffer works + m_buffersAvailable--; + } + + + if (buffersAvailable) { + if (buffersAvailable == 1) + emit bufferAvailableChanged(false); + + const char* bufferData = 0; + int bufferSize = 0; + + GstSample *sample = gst_app_sink_pull_sample(m_appSink); + GstBuffer *buffer = gst_sample_get_buffer(sample); + GstMapInfo mapInfo; + gst_buffer_map(buffer, &mapInfo, GST_MAP_READ); + bufferData = (const char*)mapInfo.data; + bufferSize = mapInfo.size; + QAudioFormat format = QGstUtils::audioFormatForSample(sample); + + if (format.isValid()) { + // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer. + // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer. + qint64 position = getPositionFromBuffer(buffer); + audioBuffer = QAudioBuffer(QByteArray((const char*)bufferData, bufferSize), format, position); + position /= 1000; // convert to milliseconds + if (position != m_position) { + m_position = position; + emit positionChanged(m_position); + } + } + gst_buffer_unmap(buffer, &mapInfo); + gst_sample_unref(sample); + } + + return audioBuffer; } bool QGstreamerAudioDecoderControl::bufferAvailable() const { - return m_session->bufferAvailable(); + QMutexLocker locker(&m_buffersMutex); + return m_buffersAvailable > 0; } qint64 QGstreamerAudioDecoderControl::position() const { - return m_session->position(); + return m_position; } qint64 QGstreamerAudioDecoderControl::duration() const { - return m_session->duration(); + return m_duration; +} + +void QGstreamerAudioDecoderControl::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString) +{ + stop(); + emit error(int(errorCode), errorString); +} + +GstFlowReturn QGstreamerAudioDecoderControl::new_sample(GstAppSink *, gpointer user_data) +{ + // "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()." + QGstreamerAudioDecoderControl *control = reinterpret_cast<QGstreamerAudioDecoderControl*>(user_data); + + int buffersAvailable; + { + QMutexLocker locker(&control->m_buffersMutex); + buffersAvailable = control->m_buffersAvailable; + control->m_buffersAvailable++; + Q_ASSERT(control->m_buffersAvailable <= MAX_BUFFERS_IN_QUEUE); + } + + if (!buffersAvailable) + QMetaObject::invokeMethod(control, "bufferAvailableChanged", Qt::QueuedConnection, Q_ARG(bool, true)); + QMetaObject::invokeMethod(control, "bufferReady", Qt::QueuedConnection); + return GST_FLOW_OK; +} + +void QGstreamerAudioDecoderControl::setAudioFlags(bool wantNativeAudio) +{ + int flags = 0; + if (m_playbin) { + g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL); + // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired + // it prevents audio format conversion + flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO); + flags |= GST_PLAY_FLAG_AUDIO; + if (wantNativeAudio) + flags |= GST_PLAY_FLAG_NATIVE_AUDIO; + g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL); + } +} + +void QGstreamerAudioDecoderControl::addAppSink() +{ + if (m_appSink) + return; + + m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL); + + GstAppSinkCallbacks callbacks; + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.new_sample = &new_sample; + gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL); + gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE); + gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE); + + gst_bin_add(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); + gst_element_link(m_audioConvert, GST_ELEMENT(m_appSink)); +} + +void QGstreamerAudioDecoderControl::removeAppSink() +{ + if (!m_appSink) + return; + + gst_element_unlink(m_audioConvert, GST_ELEMENT(m_appSink)); + gst_bin_remove(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); + + m_appSink = 0; +} + +void QGstreamerAudioDecoderControl::updateDuration() +{ + gint64 gstDuration = 0; + int duration = -1; + + if (m_playbin && qt_gst_element_query_duration(m_playbin, GST_FORMAT_TIME, &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--; + } +} + +qint64 QGstreamerAudioDecoderControl::getPositionFromBuffer(GstBuffer* buffer) +{ + qint64 position = GST_BUFFER_TIMESTAMP(buffer); + if (position >= 0) + position = position / G_GINT64_CONSTANT(1000); // microseconds + else + position = -1; + return position; } QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.h b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.h index f5e26e553..9e94088a8 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.h +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodercontrol.h @@ -37,39 +37,46 @@ ** ****************************************************************************/ -#ifndef QGSTREAMERPLAYERCONTROL_H -#define QGSTREAMERPLAYERCONTROL_H - -#include <QtCore/qobject.h> -#include <QtCore/qstack.h> - -#include <qaudioformat.h> -#include <qaudiobuffer.h> -#include <qaudiodecoder.h> -#include <qaudiodecodercontrol.h> - -#include <limits.h> +#ifndef QGSTREAMERAUDIODECODERCONTROL_H +#define QGSTREAMERAUDIODECODERCONTROL_H + +#include <QtMultimedia/private/qtmultimediaglobal_p.h> +#include <QObject> +#include <QtCore/qmutex.h> +#include "qaudiodecodercontrol.h" +#include <private/qgstreamerbushelper_p.h> +#include "qaudiodecoder.h" + +#if QT_CONFIG(gstreamer_app) +#include <private/qgstappsrc_p.h> +#endif +#include <gst/gst.h> +#include <gst/app/gstappsink.h> QT_BEGIN_NAMESPACE -class QGstreamerAudioDecoderSession; -class QGstreamerAudioDecoderService; +class QGstreamerBusHelper; +class QGstreamerMessage; -class QGstreamerAudioDecoderControl : public QAudioDecoderControl +class QGstreamerAudioDecoderControl + : public QAudioDecoderControl, + public QGstreamerBusMessageFilter { - Q_OBJECT +Q_OBJECT +Q_INTERFACES(QGstreamerBusMessageFilter) public: - QGstreamerAudioDecoderControl(QGstreamerAudioDecoderSession *session, QObject *parent = 0); - ~QGstreamerAudioDecoderControl(); + QGstreamerAudioDecoderControl(QObject *parent); + virtual ~QGstreamerAudioDecoderControl(); - QAudioDecoder::State state() const override; + // QAudioDecoder interface + QAudioDecoder::State state() const override { return m_state; } QString sourceFilename() const override; void setSourceFilename(const QString &fileName) override; - QIODevice* sourceDevice() const override; + QIODevice *sourceDevice() const override; void setSourceDevice(QIODevice *device) override; void start() override; @@ -84,12 +91,56 @@ public: qint64 position() const override; qint64 duration() const override; + // GStreamerBusMessageFilter interface + bool processBusMessage(const QGstreamerMessage &message) override; + + QGstreamerBusHelper *bus() const { return m_busHelper; } + QAudioDecoder::State pendingState() const { return m_pendingState; } + +#if QT_CONFIG(gstreamer_app) + QGstAppSrc *appsrc() const { return m_appSrc; } + static void configureAppSrcElement(GObject*, GObject*, GParamSpec*, QGstreamerAudioDecoderControl *_this); +#endif + + static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data); + +private slots: + void updateDuration(); + private: - // Stuff goes here + void setAudioFlags(bool wantNativeAudio); + void addAppSink(); + void removeAppSink(); + + void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString); + static qint64 getPositionFromBuffer(GstBuffer* buffer); + + QAudioDecoder::State m_state; + QAudioDecoder::State m_pendingState; + QGstreamerBusHelper *m_busHelper; + GstBus *m_bus; + GstElement *m_playbin; + GstElement *m_outputBin; + GstElement *m_audioConvert; + GstAppSink *m_appSink; + +#if QT_CONFIG(gstreamer_app) + QGstAppSrc *m_appSrc; +#endif + + QString mSource; + QIODevice *mDevice; // QWeakPointer perhaps + QAudioFormat mFormat; - QGstreamerAudioDecoderSession *m_session; + mutable QMutex m_buffersMutex; + int m_buffersAvailable; + + qint64 m_position; + qint64 m_duration; + + int m_durationQueries; }; QT_END_NAMESPACE -#endif +#endif // QGSTREAMERPLAYERSESSION_H diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.cpp b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.cpp index 67b49be7e..3497ab18e 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.cpp +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.cpp @@ -42,15 +42,13 @@ #include "qgstreameraudiodecoderservice.h" #include "qgstreameraudiodecodercontrol.h" -#include "qgstreameraudiodecodersession.h" QT_BEGIN_NAMESPACE QGstreamerAudioDecoderService::QGstreamerAudioDecoderService(QObject *parent) : QMediaService(parent) { - m_session = new QGstreamerAudioDecoderSession(this); - m_control = new QGstreamerAudioDecoderControl(m_session, this); + m_control = new QGstreamerAudioDecoderControl(this); } QGstreamerAudioDecoderService::~QGstreamerAudioDecoderService() diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.h b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.h index 6e52b9d87..d6e003339 100644 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.h +++ b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecoderservice.h @@ -47,7 +47,7 @@ QT_BEGIN_NAMESPACE class QGstreamerAudioDecoderControl; -class QGstreamerAudioDecoderSession; +class QGstreamerAudioDecoderControl; class QGstreamerAudioDecoderService : public QMediaService { @@ -61,7 +61,6 @@ public: private: QGstreamerAudioDecoderControl *m_control; - QGstreamerAudioDecoderSession *m_session; }; QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp deleted file mode 100644 index 185494f65..000000000 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.cpp +++ /dev/null @@ -1,599 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ -//#define DEBUG_DECODER - -#include "qgstreameraudiodecodersession.h" -#include <private/qgstreamerbushelper_p.h> - -#include <private/qgstutils_p.h> - -#include <gst/gstvalue.h> -#include <gst/base/gstbasesrc.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 <QtCore/qstandardpaths.h> -#include <QtCore/qurl.h> - -#define MAX_BUFFERS_IN_QUEUE 4 - -QT_BEGIN_NAMESPACE - -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; - -QGstreamerAudioDecoderSession::QGstreamerAudioDecoderSession(QObject *parent) - : QObject(parent), - m_state(QAudioDecoder::StoppedState), - m_pendingState(QAudioDecoder::StoppedState), - m_busHelper(0), - m_bus(0), - m_playbin(0), - m_outputBin(0), - m_audioConvert(0), - m_appSink(0), -#if QT_CONFIG(gstreamer_app) - m_appSrc(0), -#endif - mDevice(0), - m_buffersAvailable(0), - m_position(-1), - m_duration(-1), - m_durationQueries(0) -{ - // Create pipeline here - m_playbin = gst_element_factory_make(QT_GSTREAMER_PLAYBIN_ELEMENT_NAME, NULL); - - if (m_playbin != 0) { - // Sort out messages - m_bus = gst_element_get_bus(m_playbin); - m_busHelper = new QGstreamerBusHelper(m_bus, this); - m_busHelper->installMessageFilter(this); - - // Set the rest of the pipeline up - setAudioFlags(true); - - m_audioConvert = gst_element_factory_make("audioconvert", NULL); - - m_outputBin = gst_bin_new("audio-output-bin"); - gst_bin_add(GST_BIN(m_outputBin), m_audioConvert); - - // add ghostpad - GstPad *pad = gst_element_get_static_pad(m_audioConvert, "sink"); - Q_ASSERT(pad); - gst_element_add_pad(GST_ELEMENT(m_outputBin), gst_ghost_pad_new("sink", pad)); - gst_object_unref(GST_OBJECT(pad)); - - g_object_set(G_OBJECT(m_playbin), "audio-sink", m_outputBin, NULL); -#if QT_CONFIG(gstreamer_app) - g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderSession::configureAppSrcElement, (gpointer)this); -#endif - - // Set volume to 100% - gdouble volume = 1.0; - g_object_set(G_OBJECT(m_playbin), "volume", volume, NULL); - } -} - -QGstreamerAudioDecoderSession::~QGstreamerAudioDecoderSession() -{ - if (m_playbin) { - stop(); - - delete m_busHelper; -#if QT_CONFIG(gstreamer_app) - delete m_appSrc; -#endif - gst_object_unref(GST_OBJECT(m_bus)); - gst_object_unref(GST_OBJECT(m_playbin)); - } -} - -#if QT_CONFIG(gstreamer_app) -void QGstreamerAudioDecoderSession::configureAppSrcElement(GObject* object, GObject *orig, GParamSpec *pspec, QGstreamerAudioDecoderSession* self) -{ - Q_UNUSED(object); - Q_UNUSED(pspec); - - // In case we switch from appsrc to not - if (!self->appsrc()) - return; - - GstElement *appsrc; - g_object_get(orig, "source", &appsrc, NULL); - - if (!self->appsrc()->setup(appsrc)) - qWarning()<<"Could not setup appsrc element"; - - g_object_unref(G_OBJECT(appsrc)); -} -#endif - -bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &message) -{ - GstMessage* gm = message.rawMessage(); - if (gm) { - if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_DURATION) { - updateDuration(); - } else 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_DECODER - 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]) << "internal" << m_state; -#endif - - QAudioDecoder::State prevState = m_state; - - switch (newState) { - case GST_STATE_VOID_PENDING: - case GST_STATE_NULL: - m_state = QAudioDecoder::StoppedState; - break; - case GST_STATE_READY: - m_state = QAudioDecoder::StoppedState; - break; - case GST_STATE_PLAYING: - m_state = QAudioDecoder::DecodingState; - break; - case GST_STATE_PAUSED: - m_state = QAudioDecoder::DecodingState; - - //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(); - break; - } - - if (prevState != m_state) - emit stateChanged(m_state); - } - break; - - case GST_MESSAGE_EOS: - m_pendingState = m_state = QAudioDecoder::StoppedState; - emit finished(); - emit stateChanged(m_state); - 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(QAudioDecoder::FormatError, tr("Cannot play stream of type: <unknown>")); - else - processInvalidMedia(QAudioDecoder::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; -#ifdef DEBUG_DECODER - case GST_MESSAGE_INFO: - { - GError *err; - gchar *debug; - gst_message_parse_info (gm, &err, &debug); - qDebug() << "Info:" << QString::fromUtf8(err->message); - g_error_free (err); - g_free (debug); - } - break; -#endif - default: - break; - } - } else if (GST_MESSAGE_TYPE(gm) == GST_MESSAGE_ERROR) { - GError *err; - gchar *debug; - gst_message_parse_error(gm, &err, &debug); - QAudioDecoder::Error qerror = QAudioDecoder::ResourceError; - if (err->domain == GST_STREAM_ERROR) { - switch (err->code) { - case GST_STREAM_ERROR_DECRYPT: - case GST_STREAM_ERROR_DECRYPT_NOKEY: - qerror = QAudioDecoder::AccessDeniedError; - break; - case GST_STREAM_ERROR_FORMAT: - case GST_STREAM_ERROR_DEMUX: - case GST_STREAM_ERROR_DECODE: - case GST_STREAM_ERROR_WRONG_TYPE: - case GST_STREAM_ERROR_TYPE_NOT_FOUND: - case GST_STREAM_ERROR_CODEC_NOT_FOUND: - qerror = QAudioDecoder::FormatError; - break; - default: - break; - } - } else if (err->domain == GST_CORE_ERROR) { - switch (err->code) { - case GST_CORE_ERROR_MISSING_PLUGIN: - qerror = QAudioDecoder::FormatError; - break; - default: - break; - } - } - - processInvalidMedia(qerror, QString::fromUtf8(err->message)); - g_error_free(err); - g_free(debug); - } - } - - return false; -} - -QString QGstreamerAudioDecoderSession::sourceFilename() const -{ - return mSource; -} - -void QGstreamerAudioDecoderSession::setSourceFilename(const QString &fileName) -{ - stop(); - mDevice = 0; -#if QT_CONFIG(gstreamer_app) - if (m_appSrc) - m_appSrc->deleteLater(); - m_appSrc = 0; -#endif - - bool isSignalRequired = (mSource != fileName); - mSource = fileName; - if (isSignalRequired) - emit sourceChanged(); -} - -QIODevice *QGstreamerAudioDecoderSession::sourceDevice() const -{ - return mDevice; -} - -void QGstreamerAudioDecoderSession::setSourceDevice(QIODevice *device) -{ - stop(); - mSource.clear(); - bool isSignalRequired = (mDevice != device); - mDevice = device; - if (isSignalRequired) - emit sourceChanged(); -} - -void QGstreamerAudioDecoderSession::start() -{ - if (!m_playbin) { - processInvalidMedia(QAudioDecoder::ResourceError, "Playbin element is not valid"); - return; - } - - addAppSink(); - - if (!mSource.isEmpty()) { - g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL); - } else if (mDevice) { -#if QT_CONFIG(gstreamer_app) - // make sure we can read from device - if (!mDevice->isOpen() || !mDevice->isReadable()) { - processInvalidMedia(QAudioDecoder::AccessDeniedError, "Unable to read from specified device"); - return; - } - - if (!m_appSrc) - m_appSrc = new QGstAppSrc(this); - m_appSrc->setStream(mDevice); - - g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL); -#endif - } else { - return; - } - - // Set audio format - if (m_appSink) { - if (mFormat.isValid()) { - setAudioFlags(false); - GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat); - gst_app_sink_set_caps(m_appSink, caps); - gst_caps_unref(caps); - } else { - // We want whatever the native audio format is - setAudioFlags(true); - gst_app_sink_set_caps(m_appSink, NULL); - } - } - - m_pendingState = QAudioDecoder::DecodingState; - if (gst_element_set_state(m_playbin, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) { - qWarning() << "GStreamer; Unable to start decoding process"; - m_pendingState = m_state = QAudioDecoder::StoppedState; - - emit stateChanged(m_state); - } -} - -void QGstreamerAudioDecoderSession::stop() -{ - if (m_playbin) { - gst_element_set_state(m_playbin, GST_STATE_NULL); - removeAppSink(); - - QAudioDecoder::State oldState = m_state; - m_pendingState = m_state = QAudioDecoder::StoppedState; - - // GStreamer thread is stopped. Can safely access m_buffersAvailable - if (m_buffersAvailable != 0) { - m_buffersAvailable = 0; - emit bufferAvailableChanged(false); - } - - if (m_position != -1) { - m_position = -1; - emit positionChanged(m_position); - } - - if (m_duration != -1) { - m_duration = -1; - emit durationChanged(m_duration); - } - - if (oldState != m_state) - emit stateChanged(m_state); - } -} - -QAudioFormat QGstreamerAudioDecoderSession::audioFormat() const -{ - return mFormat; -} - -void QGstreamerAudioDecoderSession::setAudioFormat(const QAudioFormat &format) -{ - if (mFormat != format) { - mFormat = format; - emit formatChanged(mFormat); - } -} - -QAudioBuffer QGstreamerAudioDecoderSession::read() -{ - QAudioBuffer audioBuffer; - - int buffersAvailable; - { - QMutexLocker locker(&m_buffersMutex); - buffersAvailable = m_buffersAvailable; - - // need to decrement before pulling a buffer - // to make sure assert in QGstreamerAudioDecoderSession::new_buffer works - m_buffersAvailable--; - } - - - if (buffersAvailable) { - if (buffersAvailable == 1) - emit bufferAvailableChanged(false); - - const char* bufferData = 0; - int bufferSize = 0; - - GstSample *sample = gst_app_sink_pull_sample(m_appSink); - GstBuffer *buffer = gst_sample_get_buffer(sample); - GstMapInfo mapInfo; - gst_buffer_map(buffer, &mapInfo, GST_MAP_READ); - bufferData = (const char*)mapInfo.data; - bufferSize = mapInfo.size; - QAudioFormat format = QGstUtils::audioFormatForSample(sample); - - if (format.isValid()) { - // XXX At the moment we have to copy data from GstBuffer into QAudioBuffer. - // We could improve performance by implementing QAbstractAudioBuffer for GstBuffer. - qint64 position = getPositionFromBuffer(buffer); - audioBuffer = QAudioBuffer(QByteArray((const char*)bufferData, bufferSize), format, position); - position /= 1000; // convert to milliseconds - if (position != m_position) { - m_position = position; - emit positionChanged(m_position); - } - } - gst_buffer_unmap(buffer, &mapInfo); - gst_sample_unref(sample); - } - - return audioBuffer; -} - -bool QGstreamerAudioDecoderSession::bufferAvailable() const -{ - QMutexLocker locker(&m_buffersMutex); - return m_buffersAvailable > 0; -} - -qint64 QGstreamerAudioDecoderSession::position() const -{ - return m_position; -} - -qint64 QGstreamerAudioDecoderSession::duration() const -{ - return m_duration; -} - -void QGstreamerAudioDecoderSession::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString) -{ - stop(); - emit error(int(errorCode), errorString); -} - -GstFlowReturn QGstreamerAudioDecoderSession::new_sample(GstAppSink *, gpointer user_data) -{ - // "Note that the preroll buffer will also be returned as the first buffer when calling gst_app_sink_pull_buffer()." - QGstreamerAudioDecoderSession *session = reinterpret_cast<QGstreamerAudioDecoderSession*>(user_data); - - int buffersAvailable; - { - QMutexLocker locker(&session->m_buffersMutex); - buffersAvailable = session->m_buffersAvailable; - session->m_buffersAvailable++; - Q_ASSERT(session->m_buffersAvailable <= MAX_BUFFERS_IN_QUEUE); - } - - if (!buffersAvailable) - QMetaObject::invokeMethod(session, "bufferAvailableChanged", Qt::QueuedConnection, Q_ARG(bool, true)); - QMetaObject::invokeMethod(session, "bufferReady", Qt::QueuedConnection); - return GST_FLOW_OK; -} - -void QGstreamerAudioDecoderSession::setAudioFlags(bool wantNativeAudio) -{ - int flags = 0; - if (m_playbin) { - g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL); - // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO unless desired - // it prevents audio format conversion - flags &= ~(GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_NATIVE_VIDEO | GST_PLAY_FLAG_TEXT | GST_PLAY_FLAG_VIS | GST_PLAY_FLAG_NATIVE_AUDIO); - flags |= GST_PLAY_FLAG_AUDIO; - if (wantNativeAudio) - flags |= GST_PLAY_FLAG_NATIVE_AUDIO; - g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL); - } -} - -void QGstreamerAudioDecoderSession::addAppSink() -{ - if (m_appSink) - return; - - m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL); - - GstAppSinkCallbacks callbacks; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.new_sample = &new_sample; - gst_app_sink_set_callbacks(m_appSink, &callbacks, this, NULL); - gst_app_sink_set_max_buffers(m_appSink, MAX_BUFFERS_IN_QUEUE); - gst_base_sink_set_sync(GST_BASE_SINK(m_appSink), FALSE); - - gst_bin_add(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); - gst_element_link(m_audioConvert, GST_ELEMENT(m_appSink)); -} - -void QGstreamerAudioDecoderSession::removeAppSink() -{ - if (!m_appSink) - return; - - gst_element_unlink(m_audioConvert, GST_ELEMENT(m_appSink)); - gst_bin_remove(GST_BIN(m_outputBin), GST_ELEMENT(m_appSink)); - - m_appSink = 0; -} - -void QGstreamerAudioDecoderSession::updateDuration() -{ - gint64 gstDuration = 0; - int duration = -1; - - if (m_playbin && qt_gst_element_query_duration(m_playbin, GST_FORMAT_TIME, &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--; - } -} - -qint64 QGstreamerAudioDecoderSession::getPositionFromBuffer(GstBuffer* buffer) -{ - qint64 position = GST_BUFFER_TIMESTAMP(buffer); - if (position >= 0) - position = position / G_GINT64_CONSTANT(1000); // microseconds - else - position = -1; - return position; -} - -QT_END_NAMESPACE diff --git a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h b/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h deleted file mode 100644 index 385908cbd..000000000 --- a/src/plugins/gstreamer/audiodecoder/qgstreameraudiodecodersession.h +++ /dev/null @@ -1,157 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QGSTREAMERPLAYERSESSION_H -#define QGSTREAMERPLAYERSESSION_H - -#include <QtMultimedia/private/qtmultimediaglobal_p.h> -#include <QObject> -#include <QtCore/qmutex.h> -#include "qgstreameraudiodecodercontrol.h" -#include <private/qgstreamerbushelper_p.h> -#include "qaudiodecoder.h" - -#if QT_CONFIG(gstreamer_app) -#include <private/qgstappsrc_p.h> -#endif - -#include <gst/gst.h> -#include <gst/app/gstappsink.h> - -QT_BEGIN_NAMESPACE - -class QGstreamerBusHelper; -class QGstreamerMessage; - -class QGstreamerAudioDecoderSession : public QObject, - public QGstreamerBusMessageFilter -{ -Q_OBJECT -Q_INTERFACES(QGstreamerBusMessageFilter) - -public: - QGstreamerAudioDecoderSession(QObject *parent); - virtual ~QGstreamerAudioDecoderSession(); - - QGstreamerBusHelper *bus() const { return m_busHelper; } - - QAudioDecoder::State state() const { return m_state; } - QAudioDecoder::State pendingState() const { return m_pendingState; } - - bool processBusMessage(const QGstreamerMessage &message) override; - -#if QT_CONFIG(gstreamer_app) - QGstAppSrc *appsrc() const { return m_appSrc; } - static void configureAppSrcElement(GObject*, GObject*, GParamSpec*,QGstreamerAudioDecoderSession* _this); -#endif - - QString sourceFilename() const; - void setSourceFilename(const QString &fileName); - - QIODevice* sourceDevice() const; - void setSourceDevice(QIODevice *device); - - void start(); - void stop(); - - QAudioFormat audioFormat() const; - void setAudioFormat(const QAudioFormat &format); - - QAudioBuffer read(); - bool bufferAvailable() const; - - qint64 position() const; - qint64 duration() const; - - static GstFlowReturn new_sample(GstAppSink *sink, gpointer user_data); - -signals: - void stateChanged(QAudioDecoder::State newState); - void formatChanged(const QAudioFormat &format); - void sourceChanged(); - - void error(int error, const QString &errorString); - - void bufferReady(); - void bufferAvailableChanged(bool available); - void finished(); - - void positionChanged(qint64 position); - void durationChanged(qint64 duration); - -private slots: - void updateDuration(); - -private: - void setAudioFlags(bool wantNativeAudio); - void addAppSink(); - void removeAppSink(); - - void processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString); - static qint64 getPositionFromBuffer(GstBuffer* buffer); - - QAudioDecoder::State m_state; - QAudioDecoder::State m_pendingState; - QGstreamerBusHelper *m_busHelper; - GstBus *m_bus; - GstElement *m_playbin; - GstElement *m_outputBin; - GstElement *m_audioConvert; - GstAppSink *m_appSink; - -#if QT_CONFIG(gstreamer_app) - QGstAppSrc *m_appSrc; -#endif - - QString mSource; - QIODevice *mDevice; // QWeakPointer perhaps - QAudioFormat mFormat; - - mutable QMutex m_buffersMutex; - int m_buffersAvailable; - - qint64 m_position; - qint64 m_duration; - - int m_durationQueries; -}; - -QT_END_NAMESPACE - -#endif // QGSTREAMERPLAYERSESSION_H |