}
/*!
- *Returns audio format for caps.
+ Returns audio format for caps.
If caps doesn't have a valid audio format, an empty QAudioFormat is returned.
*/
return format;
}
+
+/*!
+ Returns audio format for a buffer.
+ If the buffer doesn't have a valid audio format, an empty QAudioFormat is returned.
+*/
+
+QAudioFormat QGstUtils::audioFormatForBuffer(GstBuffer *buffer)
+{
+ GstCaps* caps = gst_buffer_get_caps(buffer);
+ if (!caps)
+ return QAudioFormat();
+
+ QAudioFormat format = QGstUtils::audioFormatForCaps(caps);
+ gst_caps_unref(caps);
+ return format;
+}
+
+
+/*!
+ Builds GstCaps for an audio format.
+ Returns 0 if the audio format is not valid.
+ Caller must unref GstCaps.
+*/
+
+GstCaps *QGstUtils::capsForAudioFormat(QAudioFormat format)
+{
+ GstStructure *structure = 0;
+
+ if (format.isValid()) {
+ if (format.sampleType() == QAudioFormat::SignedInt || format.sampleType() == QAudioFormat::UnSignedInt) {
+ structure = gst_structure_new("audio/x-raw-int", NULL);
+ } else if (format.sampleType() == QAudioFormat::Float) {
+ structure = gst_structure_new("audio/x-raw-float", NULL);
+ }
+ }
+
+ GstCaps *caps = 0;
+
+ if (structure) {
+ gst_structure_set(structure, "rate", G_TYPE_INT, format.sampleRate(), NULL);
+ gst_structure_set(structure, "channels", G_TYPE_INT, format.channelCount(), NULL);
+ gst_structure_set(structure, "width", G_TYPE_INT, format.sampleSize(), NULL);
+ gst_structure_set(structure, "depth", G_TYPE_INT, format.sampleSize(), NULL);
+
+ if (format.byteOrder() == QAudioFormat::LittleEndian)
+ gst_structure_set(structure, "endianness", G_TYPE_INT, 1234, NULL);
+ else if (format.byteOrder() == QAudioFormat::BigEndian)
+ gst_structure_set(structure, "endianness", G_TYPE_INT, 4321, NULL);
+
+ if (format.sampleType() == QAudioFormat::SignedInt)
+ gst_structure_set(structure, "signed", G_TYPE_BOOLEAN, TRUE, NULL);
+ else if (format.sampleType() == QAudioFormat::UnSignedInt)
+ gst_structure_set(structure, "signed", G_TYPE_BOOLEAN, FALSE, NULL);
+
+ caps = gst_caps_new_empty();
+ Q_ASSERT(caps);
+ gst_caps_append_structure(caps, structure);
+ }
+
+ return caps;
+}
+
QT_END_NAMESPACE
#include <QtCore/qdebug.h>
#include <QtCore/qdir.h>
#include <QtCore/qstandardpaths.h>
+#include <QtCore/qurl.h>
+
+#define MAX_BUFFERS_IN_QUEUE 5
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_busHelper(0),
m_bus(0),
m_playbin(0),
+ m_appSink(0),
#if defined(HAVE_GST_APPSRC)
m_appSrc(0),
#endif
- mDevice(0)
+ mDevice(0),
+ m_buffersAvailable(0)
{
// Default format
mFormat.setChannels(2);
mFormat.setSampleSize(16);
mFormat.setFrequency(48000);
- mFormat.setCodec("audio/x-raw");
+ mFormat.setCodec("audio/pcm");
mFormat.setSampleType(QAudioFormat::UnSignedInt);
// Create pipeline here
-#if 0
+ m_playbin = gst_element_factory_make("playbin2", NULL);
+
if (m_playbin != 0) {
+
+ int flags = 0;
+ g_object_get(G_OBJECT(m_playbin), "flags", &flags, NULL);
+ // make sure not to use GST_PLAY_FLAG_NATIVE_AUDIO, 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;
+ g_object_set(G_OBJECT(m_playbin), "flags", flags, NULL);
+
+ m_appSink = (GstAppSink*)gst_element_factory_make("appsink", NULL);
+ gst_object_ref(GST_OBJECT(m_appSink));
+
+ GstAppSinkCallbacks callbacks;
+ memset(&callbacks, 0, sizeof(callbacks));
+ callbacks.new_buffer = &new_buffer;
+ 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);
+
+ GstElement *audioConvert = gst_element_factory_make("audioconvert", NULL);
+
+ GstElement *bin = gst_bin_new("audio-output-bin");
+ gst_bin_add(GST_BIN(bin), audioConvert);
+ gst_bin_add(GST_BIN(bin), GST_ELEMENT(m_appSink));
+ gst_element_link(audioConvert, GST_ELEMENT(m_appSink));
+
+ // add ghostpad
+ GstPad *pad = gst_element_get_static_pad(audioConvert, "sink");
+ Q_ASSERT(pad);
+ gst_element_add_pad(GST_ELEMENT(bin), gst_ghost_pad_new("sink", pad));
+ gst_object_unref(GST_OBJECT(pad));
+
// Sort out messages
m_bus = gst_element_get_bus(m_playbin);
m_busHelper = new QGstreamerBusHelper(m_bus, this);
m_busHelper->installMessageFilter(this);
+
+ g_object_set(G_OBJECT(m_playbin), "audio-sink", bin, NULL);
+
+ // Set volume to 100%
+ gdouble volume = 1.0;
+ g_object_set(G_OBJECT(m_playbin), "volume", volume, NULL);
}
-#endif
}
QGstreamerAudioDecoderSession::~QGstreamerAudioDecoderSession()
delete m_busHelper;
gst_object_unref(GST_OBJECT(m_bus));
gst_object_unref(GST_OBJECT(m_playbin));
+ gst_object_unref(GST_OBJECT(m_appSink));
}
}
}
#endif
-#if 0
-void QGstreamerAudioDecoderSession::loadFromStream(const QNetworkRequest &request, QIODevice *appSrcStream)
-{
-#if defined(HAVE_GST_APPSRC)
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO;
-#endif
- 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) &QGstreamerAudioDecoderSession::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 QGstreamerAudioDecoderSession::loadFromUri(const QNetworkRequest &request)
-{
-#ifdef DEBUG_PLAYBIN
- qDebug() << Q_FUNC_INFO << request.url();
-#endif
- 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();
- }
- }
-}
-#endif
-
bool QGstreamerAudioDecoderSession::processBusMessage(const QGstreamerMessage &message)
{
GstMessage* gm = message.rawMessage();
{
stop();
mDevice = 0;
+ bool isSignalRequired = (mSource != fileName);
mSource = fileName;
+ if (isSignalRequired)
+ emit sourceChanged();
}
QIODevice *QGstreamerAudioDecoderSession::sourceDevice() const
{
stop();
mSource.clear();
+ bool isSignalRequired = (mDevice != device);
mDevice = device;
+ if (isSignalRequired)
+ emit sourceChanged();
}
void QGstreamerAudioDecoderSession::start()
{
- // TODO
+ if (!m_playbin) {
+ processInvalidMedia(QAudioDecoder::ResourceError, "Playbin element is not valid");
+ return;
+ }
+
+ if (!mSource.isEmpty()) {
+ g_object_set(G_OBJECT(m_playbin), "uri", QUrl::fromLocalFile(mSource).toEncoded().constData(), NULL);
+ } else if (mDevice) {
+ // 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->deleteLater();
+ m_appSrc = new QGstAppSrc(this);
+ m_appSrc->setStream(mDevice);
+
+ g_signal_connect(G_OBJECT(m_playbin), "deep-notify::source", (GCallback) &QGstreamerAudioDecoderSession::configureAppSrcElement, (gpointer)this);
+ g_object_set(G_OBJECT(m_playbin), "uri", "appsrc://", NULL);
+ } else {
+ return;
+ }
+
+ // Set audio format
+ if (m_appSink) {
+ GstCaps *caps = QGstUtils::capsForAudioFormat(mFormat);
+ gst_app_sink_set_caps(m_appSink, caps); // appsink unrefs caps
+ }
+
+ 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()
{
- // TODO
+ if (m_playbin) {
+ gst_element_set_state(m_playbin, GST_STATE_NULL);
+
+ 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 (oldState != m_state)
+ emit stateChanged(m_state);
+ }
}
QAudioFormat QGstreamerAudioDecoderSession::audioFormat() const
QAudioBuffer QGstreamerAudioDecoderSession::read(bool *ok)
{
- // TODO
+ 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);
+
+ GstBuffer *buffer = gst_app_sink_pull_buffer(m_appSink);
+
+ QAudioFormat format = QGstUtils::audioFormatForBuffer(buffer);
+ 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.
+ audioBuffer = QAudioBuffer(QByteArray((const char*)buffer->data, buffer->size), format);
+ }
+ gst_buffer_unref(buffer);
+ }
+
if (ok)
- *ok = false;
- return QAudioBuffer();
+ *ok = audioBuffer.isValid();
+ return audioBuffer;
}
bool QGstreamerAudioDecoderSession::bufferAvailable() const
{
- return false;
+ QMutexLocker locker(&m_buffersMutex);
+ return m_buffersAvailable;
}
void QGstreamerAudioDecoderSession::processInvalidMedia(QAudioDecoder::Error errorCode, const QString& errorString)
emit error(int(errorCode), errorString);
}
+GstFlowReturn QGstreamerAudioDecoderSession::new_buffer(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;
+}
+
QT_END_NAMESPACE
--- /dev/null
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** 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.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest/QtTest>
+#include <QDebug>
+#include "qaudiodecoder_p.h"
+
+#define TEST_FILE_NAME "testdata/test.wav"
+
+QT_USE_NAMESPACE
+
+/*
+ This is the backend conformance test.
+
+ Since it relies on platform media framework and sound hardware
+ it may be less stable.
+*/
+
+class tst_QAudioDecoderBackend : public QObject
+{
+ Q_OBJECT
+public slots:
+ void init();
+ void cleanup();
+ void initTestCase();
+
+private slots:
+ void decoderTest();
+};
+
+void tst_QAudioDecoderBackend::init()
+{
+}
+
+void tst_QAudioDecoderBackend::initTestCase()
+{
+}
+
+void tst_QAudioDecoderBackend::cleanup()
+{
+}
+
+void tst_QAudioDecoderBackend::decoderTest()
+{
+ QAudioDecoder d;
+ QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(d.bufferAvailable() == false);
+ QCOMPARE(d.sourceFilename(), QString(""));
+
+ // Test local file
+ QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
+ d.setSourceFilename(fileInfo.absoluteFilePath());
+ QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
+
+ QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
+ QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
+ QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
+
+ d.start();
+ QTRY_VERIFY(!stateSpy.isEmpty());
+ QTRY_VERIFY(!readySpy.isEmpty());
+ QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+ QVERIFY(d.bufferAvailable());
+
+ bool ok;
+ QAudioBuffer buffer = d.read(&ok);
+ QVERIFY(ok);
+ QVERIFY(buffer.isValid());
+ QCOMPARE(buffer.format(), d.audioFormat());
+
+ QVERIFY(errorSpy.isEmpty());
+
+ d.stop();
+ QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QVERIFY(!d.bufferAvailable());
+ readySpy.clear();
+ bufferChangedSpy.clear();
+ stateSpy.clear();
+
+ // Test source device
+ QFile file(fileInfo.absoluteFilePath());
+ QVERIFY(file.open(QIODevice::ReadOnly));
+ d.setSourceDevice(&file);
+
+ // change output audio format
+ QAudioFormat format;
+ format.setChannels(1);
+ format.setSampleSize(8);
+ format.setFrequency(8000);
+ format.setCodec("audio/pcm");
+ format.setSampleType(QAudioFormat::SignedInt);
+ d.setAudioFormat(format);
+
+ d.start();
+ QTRY_VERIFY(!stateSpy.isEmpty());
+ QTRY_VERIFY(!readySpy.isEmpty());
+ QTRY_VERIFY(!bufferChangedSpy.isEmpty());
+ QVERIFY(d.bufferAvailable());
+
+ buffer = d.read(&ok);
+ QVERIFY(ok);
+ QVERIFY(buffer.isValid());
+ QCOMPARE(buffer.format(), d.audioFormat());
+
+ QVERIFY(errorSpy.isEmpty());
+}
+
+QTEST_MAIN(tst_QAudioDecoderBackend)
+
+#include "tst_qaudiodecoderbackend.moc"