diff options
author | Michael Goddard <michael.goddard@nokia.com> | 2012-01-04 16:05:55 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-01-05 01:13:44 +0100 |
commit | 502d3c8eb353f45f988e371aa6d7938d5ec9d51e (patch) | |
tree | f8f2108e825bc13755aad7ae6cef6f9c044ffba7 /src/multimedia/audio | |
parent | 6ee1977d60b131ae00919e1f37796c1efc4906f7 (diff) |
Restructure the source code a little.
Change-Id: I995b0fb33bdda7f01bf6266c1c50a1b17eba6760
Reviewed-by: Jonas Rabbe <jonas.rabbe@nokia.com>
Diffstat (limited to 'src/multimedia/audio')
-rw-r--r-- | src/multimedia/audio/audio.pri | 47 | ||||
-rw-r--r-- | src/multimedia/audio/qsamplecache_p.cpp | 401 | ||||
-rw-r--r-- | src/multimedia/audio/qsamplecache_p.h | 161 | ||||
-rw-r--r-- | src/multimedia/audio/qsound.cpp | 236 | ||||
-rw-r--r-- | src/multimedia/audio/qsound.h | 93 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect.cpp | 311 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect.h | 135 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_pulse_p.cpp | 1100 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_pulse_p.h | 170 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_qmedia_p.cpp | 254 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_qmedia_p.h | 127 | ||||
-rw-r--r-- | src/multimedia/audio/qwavedecoder_p.cpp | 307 | ||||
-rw-r--r-- | src/multimedia/audio/qwavedecoder_p.h | 141 |
13 files changed, 3472 insertions, 11 deletions
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index 86c379d65..8c8daa5d1 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -1,17 +1,24 @@ INCLUDEPATH += audio -PUBLIC_HEADERS += audio/qaudio.h \ +PUBLIC_HEADERS += \ + audio/qaudio.h \ audio/qaudioformat.h \ audio/qaudioinput.h \ audio/qaudiooutput.h \ audio/qaudiodeviceinfo.h \ audio/qaudiosystemplugin.h \ - audio/qaudiosystem.h + audio/qaudiosystem.h \ + audio/qsoundeffect.h \ + audio/qsound.h -PRIVATE_HEADERS += audio/qaudiodevicefactory_p.h audio/qaudiopluginloader_p.h +PRIVATE_HEADERS += \ + audio/qaudiodevicefactory_p.h \ + audio/qaudiopluginloader_p.h \ + audio/qwavedecoder_p.h \ + audio/qsamplecache_p.h - -SOURCES += audio/qaudio.cpp \ +SOURCES += \ + audio/qaudio.cpp \ audio/qaudioformat.cpp \ audio/qaudiodeviceinfo.cpp \ audio/qaudiooutput.cpp \ @@ -19,7 +26,11 @@ SOURCES += audio/qaudio.cpp \ audio/qaudiosystemplugin.cpp \ audio/qaudiosystem.cpp \ audio/qaudiodevicefactory.cpp \ - audio/qaudiopluginloader.cpp + audio/qaudiopluginloader.cpp \ + audio/qsoundeffect.cpp \ + audio/qwavedecoder_p.cpp \ + audio/qsamplecache_p.cpp \ + audio/qsound.cpp mac { PRIVATE_HEADERS += audio/qaudioinput_mac_p.h \ @@ -46,15 +57,29 @@ win32 { unix:!mac { contains(config_test_pulseaudio, yes) { DEFINES += QT_NO_AUDIO_BACKEND - } - else:contains(config_test_alsa, yes) { - linux-*|freebsd-*|openbsd-* { + CONFIG += link_pkgconfig + PKGCONFIG += libpulse + + DEFINES += QT_MULTIMEDIA_PULSEAUDIO + PRIVATE_HEADERS += audio/qsoundeffect_pulse_p.h + SOURCES += audio/qsoundeffect_pulse_p.cpp + !maemo*:DEFINES += QTM_PULSEAUDIO_DEFAULTBUFFER + } else { + DEFINES += QT_MULTIMEDIA_QMEDIAPLAYER + PRIVATE_HEADERS += audio/qsoundeffect_qmedia_p.h + SOURCES += audio/qsoundeffect_qmedia_p.cpp + + contains(config_test_alsa, yes):linux-*|freebsd-*|openbsd-* { DEFINES += HAS_ALSA PRIVATE_HEADERS += audio/qaudiooutput_alsa_p.h audio/qaudioinput_alsa_p.h audio/qaudiodeviceinfo_alsa_p.h SOURCES += audio/qaudiodeviceinfo_alsa_p.cpp \ - audio/qaudiooutput_alsa_p.cpp \ - audio/qaudioinput_alsa_p.cpp + audio/qaudiooutput_alsa_p.cpp \ + audio/qaudioinput_alsa_p.cpp LIBS_PRIVATE += -lasound } } +} else { + DEFINES += QT_MULTIMEDIA_QMEDIAPLAYER + PRIVATE_HEADERS += audio/qsoundeffect_qmedia_p.h + SOURCES += audio/qsoundeffect_qmedia_p.cpp } diff --git a/src/multimedia/audio/qsamplecache_p.cpp b/src/multimedia/audio/qsamplecache_p.cpp new file mode 100644 index 000000000..1a06cd17e --- /dev/null +++ b/src/multimedia/audio/qsamplecache_p.cpp @@ -0,0 +1,401 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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 "qsamplecache_p.h" +#include "qwavedecoder_p.h" +#include <QtNetwork> + +//#define QT_SAMPLECACHE_DEBUG + +QT_BEGIN_NAMESPACE + + +/*! + \class QSampleCache + \internal + + When you want to get a sound sample data, you need to request the QSample reference from QSampleCache. + + \since 1.1 + + \code + QSample *m_sample; // class member. + + private Q_SLOTS: + void decoderError(); + void sampleReady(); + \endcode + + \code + Q_GLOBAL_STATIC(QSampleCache, sampleCache) //declare a singleton manager + \endcode + + \code + m_sample = sampleCache()->requestSample(url); + switch(m_sample->state()) { + case QSample::Ready: + sampleReady(); + break; + case QSample::Error: + decoderError(); + break; + default: + connect(m_sample, SIGNAL(error()), this, SLOT(decoderError())); + connect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady())); + break; + } + \endcode + + When you no longer need the sound sample data, you need to release it: + + \code + if (m_sample) { + m_sample->release(); + m_sample = 0; + } + \endcode +*/ + +QSampleCache::QSampleCache() + : m_networkAccessManager(0) + , m_mutex(QMutex::Recursive) + , m_capacity(0) + , m_usage(0) +{ + m_loadingThread.setObjectName(QLatin1String("QSampleCache::LoadingThread")); +} + +QNetworkAccessManager& QSampleCache::networkAccessManager() +{ + if (!m_networkAccessManager) + m_networkAccessManager = new QNetworkAccessManager(); + return *m_networkAccessManager; +} + +QSampleCache::~QSampleCache() +{ + QMutexLocker m(&m_mutex); + + m_loadingThread.quit(); + m_loadingThread.wait(); + + // Killing the loading thread means that no samples can be + // deleted using deleteLater. And some samples that had deleteLater + // already called won't have been processed (m_staleSamples) + foreach (QSample* sample, m_samples) + delete sample; + + foreach (QSample* sample, m_staleSamples) + delete sample; // deleting a sample does affect the m_staleSamples list, but foreach copies it + + delete m_networkAccessManager; +} + +QSample* QSampleCache::requestSample(const QUrl& url) +{ + if (!m_loadingThread.isRunning()) + m_loadingThread.start(); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSampleCache: request sample [" << url << "]"; +#endif + QMutexLocker locker(&m_mutex); + QMap<QUrl, QSample*>::iterator it = m_samples.find(url); + QSample* sample; + if (it == m_samples.end()) { + sample = new QSample(url, this); + m_samples.insert(url, sample); + sample->moveToThread(&m_loadingThread); + } else { + sample = *it; + } + + sample->addRef(); + locker.unlock(); + + sample->loadIfNecessary(); + return sample; +} + +void QSampleCache::setCapacity(qint64 capacity) +{ + QMutexLocker locker(&m_mutex); + if (m_capacity == capacity) + return; +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSampleCache: capacity changes from " << m_capacity << "to " << capacity; +#endif + if (m_capacity > 0 && capacity <= 0) { //memory management strategy changed + for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) { + QSample* sample = *it; + if (sample->m_ref == 0) { + unloadSample(sample); + it = m_samples.erase(it); + } else + it++; + } + } + + m_capacity = capacity; + refresh(0); +} + +// Called locked +void QSampleCache::unloadSample(QSample *sample) +{ + m_usage -= sample->m_soundData.size(); + m_staleSamples.insert(sample); + sample->deleteLater(); +} + +// Called in both threads +void QSampleCache::refresh(qint64 usageChange) +{ + QMutexLocker locker(&m_mutex); + m_usage += usageChange; + if (m_capacity <= 0 || m_usage <= m_capacity) + return; + +#ifdef QT_SAMPLECACHE_DEBUG + qint64 recoveredSize = 0; +#endif + + //free unused samples to keep usage under capacity limit. + for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) { + QSample* sample = *it; + if (sample->m_ref > 0) { + ++it; + continue; + } +#ifdef QT_SAMPLECACHE_DEBUG + recoveredSize += sample->m_soundData.size(); +#endif + unloadSample(sample); + it = m_samples.erase(it); + if (m_usage <= m_capacity) + return; + } + +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSampleCache: refresh(" << usageChange + << ") recovered size =" << recoveredSize + << "new usage =" << m_usage; +#endif + + if (m_usage > m_capacity) + qWarning() << "QSampleCache: usage[" << m_usage << " out of limit[" << m_capacity << "]"; +} + +// Called in both threads +void QSampleCache::removeUnreferencedSample(QSample *sample) +{ + QMutexLocker m(&m_mutex); + m_staleSamples.remove(sample); +} + +// Called in loader thread (since this lives in that thread) +// Also called from application thread after loader thread dies. +QSample::~QSample() +{ + // Remove ourselves from our parent + m_parent->removeUnreferencedSample(this); + + QMutexLocker locker(&m_mutex); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "~QSample" << this << ": deleted [" << m_url << "]" << QThread::currentThread(); +#endif + cleanup(); +} + +// Called in application thread +void QSample::loadIfNecessary() +{ + QMutexLocker locker(&m_mutex); + if (m_state == QSample::Error || m_state == QSample::Creating) { + m_state = QSample::Loading; + QMetaObject::invokeMethod(this, "load", Qt::QueuedConnection); + } +} + +// Called in both threads +bool QSampleCache::notifyUnreferencedSample(QSample* sample) +{ + QMutexLocker locker(&m_mutex); + if (m_capacity > 0) + return false; + m_samples.remove(sample->m_url); + m_staleSamples.insert(sample); + sample->deleteLater(); + return true; +} + +// Called in application threadd +void QSample::release() +{ + QMutexLocker locker(&m_mutex); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "Sample:: release" << this << QThread::currentThread() << m_ref; +#endif + m_ref--; + if (m_ref == 0) + m_parent->notifyUnreferencedSample(this); +} + +// Called in dtor and when stream is loaded +// must be called locked. +void QSample::cleanup() +{ + if (m_waveDecoder) + m_waveDecoder->deleteLater(); + if (m_stream) + m_stream->deleteLater(); + + m_waveDecoder = 0; + m_stream = 0; +} + +// Called in application thread +void QSample::addRef() +{ + m_ref++; +} + +// Called in loading thread +void QSample::readSample() +{ + Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); + QMutexLocker m(&m_mutex); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSample: readSample"; +#endif + qint64 read = m_waveDecoder->read(m_soundData.data() + m_sampleReadLength, + qMin(m_waveDecoder->bytesAvailable(), + qint64(m_waveDecoder->size() - m_sampleReadLength))); + if (read > 0) + m_sampleReadLength += read; + if (m_sampleReadLength < m_waveDecoder->size()) + return; + Q_ASSERT(m_sampleReadLength == qint64(m_soundData.size())); + onReady(); +} + +// Called in loading thread +void QSample::decoderReady() +{ + Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); + QMutexLocker m(&m_mutex); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSample: decoder ready"; +#endif + m_parent->refresh(m_waveDecoder->size()); + + m_soundData.resize(m_waveDecoder->size()); + m_sampleReadLength = 0; + qint64 read = m_waveDecoder->read(m_soundData.data(), m_waveDecoder->size()); + if (read > 0) + m_sampleReadLength += read; + if (m_sampleReadLength >= m_waveDecoder->size()) + onReady(); +} + +// Called in all threads +QSample::State QSample::state() const +{ + QMutexLocker m(&m_mutex); + return m_state; +} + +// Called in loading thread +// Essentially a second ctor, doesn't need locks (?) +void QSample::load() +{ + Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSample: load [" << m_url << "]"; +#endif + m_stream = m_parent->networkAccessManager().get(QNetworkRequest(m_url)); + connect(m_stream, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(decoderError())); + m_waveDecoder = new QWaveDecoder(m_stream); + connect(m_waveDecoder, SIGNAL(formatKnown()), SLOT(decoderReady())); + connect(m_waveDecoder, SIGNAL(parsingError()), SLOT(decoderError())); + connect(m_waveDecoder, SIGNAL(readyRead()), SLOT(readSample())); +} + +// Called in loading thread +void QSample::decoderError() +{ + Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); + QMutexLocker m(&m_mutex); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSample: decoder error"; +#endif + cleanup(); + m_state = QSample::Error; + emit error(); +} + +// Called in loading thread from decoder when sample is done. Locked already. +void QSample::onReady() +{ + Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread")); +#ifdef QT_SAMPLECACHE_DEBUG + qDebug() << "QSample: load ready"; +#endif + m_audioFormat = m_waveDecoder->audioFormat(); + cleanup(); + m_state = QSample::Ready; + emit ready(); +} + +// Called in application thread, then moved to loader thread +QSample::QSample(const QUrl& url, QSampleCache *parent) + : m_parent(parent) + , m_stream(0) + , m_waveDecoder(0) + , m_url(url) + , m_sampleReadLength(0) + , m_state(Creating) + , m_ref(0) +{ +} + +QT_END_NAMESPACE + +#include "moc_qsamplecache_p.cpp" diff --git a/src/multimedia/audio/qsamplecache_p.h b/src/multimedia/audio/qsamplecache_p.h new file mode 100644 index 000000000..91ca457e6 --- /dev/null +++ b/src/multimedia/audio/qsamplecache_p.h @@ -0,0 +1,161 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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$ +** +****************************************************************************/ + +#ifndef QSAMPLECACHE_P_H +#define QSAMPLECACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qthread.h> +#include <QtCore/qurl.h> +#include <QtCore/qmutex.h> +#include <QtCore/qmap.h> +#include <QtCore/qset.h> +#include <qaudioformat.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + + +class QNetworkAccessManager; +class QSampleCache; +class QWaveDecoder; + +// Lives in application thread +class QSample : public QObject +{ + Q_OBJECT +public: + friend class QSampleCache; + enum State + { + Creating, + Loading, + Error, + Ready, + }; + + State state() const; + // These are not (currently) locked because they are only meant to be called after these + // variables are updated to their final states + const QByteArray& data() const { Q_ASSERT(state() == Ready); return m_soundData; } + const QAudioFormat& format() const { Q_ASSERT(state() == Ready); return m_audioFormat; } + void release(); + +Q_SIGNALS: + void error(); + void ready(); + +protected: + QSample(const QUrl& url, QSampleCache *parent); + +private Q_SLOTS: + void load(); + void decoderError(); + void readSample(); + void decoderReady(); + +private: + void onReady(); + void cleanup(); + void addRef(); + void loadIfNecessary(); + QSample(); + ~QSample(); + + mutable QMutex m_mutex; + QSampleCache *m_parent; + QByteArray m_soundData; + QAudioFormat m_audioFormat; + QIODevice *m_stream; + QWaveDecoder *m_waveDecoder; + QUrl m_url; + qint64 m_sampleReadLength; + State m_state; + int m_ref; +}; + +class QSampleCache +{ +public: + friend class QSample; + + QSampleCache(); + ~QSampleCache(); + + QSample* requestSample(const QUrl& url); + void setCapacity(qint64 capacity); + +private: + QMap<QUrl, QSample*> m_samples; + QSet<QSample*> m_staleSamples; + QNetworkAccessManager *m_networkAccessManager; + QMutex m_mutex; + qint64 m_capacity; + qint64 m_usage; + QThread m_loadingThread; + + QNetworkAccessManager& networkAccessManager(); + void refresh(qint64 usageChange); + bool notifyUnreferencedSample(QSample* sample); + void removeUnreferencedSample(QSample* sample); + void unloadSample(QSample* sample); +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSAMPLECACHE_P_H diff --git a/src/multimedia/audio/qsound.cpp b/src/multimedia/audio/qsound.cpp new file mode 100644 index 000000000..3749764ca --- /dev/null +++ b/src/multimedia/audio/qsound.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module 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 "qsound.h" +#include "qsoundeffect.h" +#include "qcoreapplication.h" + + +/*! + \class QSound + \brief The QSound class provides a way to play .wav sound files. + + \ingroup multimedia + + + Qt provides the most commonly required audio operation in GUI + applications: asynchronously playing a sound file. This is most + easily accomplished using the static play() function: + + \snippet doc/src/snippets/multimedia-snippets/qsound.cpp 0 + + Alternatively, create a QSound object from the sound file first + and then call the play() slot: + + \snippet doc/src/snippets/multimedia-snippets/qsound.cpp 1 + + Once created a QSound object can be queried for its fileName() and + total number of loops() (i.e. the number of times the sound will + play). The number of repetitions can be altered using the + setLoops() function. While playing the sound, the loopsRemaining() + function returns the remaining number of repetitions. Use the + isFinished() function to determine whether the sound has finished + playing. + + Sounds played using a QSound object may use more memory than the + static play() function, but it may also play more immediately + (depending on the underlying platform audio facilities). + +*/ + + +/*! + Plays the sound stored in the file specified by the given \a filename. + + \since 5.0 + \sa stop(), loopsRemaining(), isFinished() +*/ +void QSound::play(const QString& filename) +{ + // Object destruction is generaly handled via deleteOnComplete + // Unexpected cases will be handled via parenting of QSound objects to qApp + QSound *sound = new QSound(filename, qApp); + sound->connect(sound->m_soundEffect, SIGNAL(playingChanged()), SLOT(deleteOnComplete())); + sound->play(); +} + +/*! + Constructs a QSound object from the file specified by the given \a + filename and with the given \a parent. + + \since 5.0 + \sa play() +*/ +QSound::QSound(const QString& filename, QObject* parent) + : QObject(parent) +{ + m_soundEffect = new QSoundEffect(this); + m_soundEffect->setSource(QUrl::fromLocalFile(filename)); +} + +/*! + Destroys this sound object. If the sound is not finished playing, + the stop() function is called before the sound object is + destroyed. + + \since 5.0 + \sa stop(), isFinished() +*/ +QSound::~QSound() +{ + if (!isFinished()) + stop(); +} + +/*! + Returns true if the sound has finished playing; otherwise returns false. +*/ +bool QSound::isFinished() const +{ + return !m_soundEffect->isPlaying(); +} + +/*! + \overload + + Starts playing the sound specified by this QSound object. + + The function returns immediately. Depending on the platform audio + facilities, other sounds may stop or be mixed with the new + sound. The sound can be played again at any time, possibly mixing + or replacing previous plays of the sound. + + \since 5.0 + \sa fileName() +*/ +void QSound::play() +{ + m_soundEffect->play(); +} + +/*! + Returns the number of times the sound will play. + Return value of \c QSound::Infinite indicates infinite number of loops + + \since 5.0 + \sa loopsRemaining(), setLoops() +*/ +int QSound::loops() const +{ + // retain old API value for infite loops + int loopCount = m_soundEffect->loopCount(); + if (loopCount == QSoundEffect::Infinite) + loopCount = Infinite; + + return loopCount; +} + +/*! + Returns the remaining number of times the sound will loop (for all + positive values this value decreases each time the sound is played). + Return value of \c QSound::Infinite indicates infinite number of loops + + \since 5.0 + \sa loops(), isFinished() +*/ +int QSound::loopsRemaining() const +{ + // retain old API value for infite loops + int loopsRemaining = m_soundEffect->loopsRemaining(); + if (loopsRemaining == QSoundEffect::Infinite) + loopsRemaining = Infinite; + + return loopsRemaining; +} + +/*! + \fn void QSound::setLoops(int number) + + Sets the sound to repeat the given \a number of times when it is + played. + + Note that passing the value \c QSound::Infinite will cause the sound to loop + indefinitely. + + \since 5.0 + \sa loops() +*/ +void QSound::setLoops(int n) +{ + if (n == Infinite) + n = QSoundEffect::Infinite; + + m_soundEffect->setLoopCount(n); +} + +/*! + Returns the filename associated with this QSound object. + + \since 5.0 + \sa QSound() +*/ +QString QSound::fileName() const +{ + return m_soundEffect->source().toLocalFile(); +} + +/*! + Stops the sound playing. + + \since 5.0 + \sa play() +*/ +void QSound::stop() +{ + m_soundEffect->stop(); +} + +/*! + \internal + \since 5.0 +*/ +void QSound::deleteOnComplete() +{ + if (!m_soundEffect->isPlaying()) + deleteLater(); +} + +#include "moc_qsound.cpp" diff --git a/src/multimedia/audio/qsound.h b/src/multimedia/audio/qsound.h new file mode 100644 index 000000000..c45ddb10f --- /dev/null +++ b/src/multimedia/audio/qsound.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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$ +** +****************************************************************************/ + +#ifndef QSOUND_H +#define QSOUND_H + +#include <qtmultimediadefs.h> +#include <QtCore/qobject.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + +class QSoundEffect; + +class Q_MULTIMEDIA_EXPORT QSound : public QObject +{ + Q_OBJECT +public: + enum Loop + { + Infinite = -1, + }; + + static void play(const QString& filename); + + explicit QSound(const QString& filename, QObject* parent = 0); + ~QSound(); + + int loops() const; + int loopsRemaining() const; + void setLoops(int); + QString fileName() const; + + bool isFinished() const; + +public Q_SLOTS: + void play(); + void stop(); + +private Q_SLOTS: + void deleteOnComplete(); + +private: + QSoundEffect *m_soundEffect; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + + +#endif // QSOUND_H diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp new file mode 100644 index 000000000..b1c359ef0 --- /dev/null +++ b/src/multimedia/audio/qsoundeffect.cpp @@ -0,0 +1,311 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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 "qsoundeffect.h" + +#if defined(QT_MULTIMEDIA_PULSEAUDIO) +#include "qsoundeffect_pulse_p.h" +#elif(QT_MULTIMEDIA_QMEDIAPLAYER) +#include "qsoundeffect_qmedia_p.h" +#endif + +QT_BEGIN_NAMESPACE + +/*! + \qmlclass SoundEffect QSoundEffect + \brief The SoundEffect element provides a way to play sound effects in QML. + \since 1.0 + + \inmodule QtMultimedia + + This element is part of the \bold{QtMultimedia 4.0} module. + + The following example plays a WAV file on mouse click. + + \snippet doc/src/snippets/multimedia-snippets/soundeffect.qml complete snippet +*/ + +/*! + \qmlproperty url SoundEffect::source + \since 1.0 + + This property provides a way to control the sound to play. +*/ + +/*! + \qmlproperty int SoundEffect::loops + \since 1.0 + + This property provides a way to control the number of times to repeat the sound on each play(). + + Set to -1 (infinite) to enable infinite loop. +*/ + +/*! + \qmlproperty qreal SoundEffect::volume + \since 1.0 + + This property holds the volume of the playback, from 0.0 (silent) to 1.0 (maximum volume). + Note: Currently this has no effect on Mac OS X. +*/ + +/*! + \qmlproperty bool SoundEffect::muted + \since 1.0 + + This property provides a way to control muting. +*/ + +/*! + \qmlproperty bool SoundEffect::playing + \since 1.1 + + This property indicates if the soundeffect is playing or not. +*/ + +/*! + \qmlproperty int SoundEffect::status + \since 1.0 + + This property indicates the following status of the soundeffect. + + Null: no source has been set or is null. + Loading: the soundeffect is trying to load the source. + Ready: the source is loaded and ready for play. + Error: some error happened during operation, such as failure of loading the source. +*/ + +/*! + \qmlsignal SoundEffect::sourceChanged() + \since 1.0 + + This handler is called when the source has changed. +*/ + +/*! + \qmlsignal SoundEffect::loopCountChanged() + \since 1.0 + + This handler is called when the initial number of loops has changed. +*/ + +/*! + \qmlsignal SoundEffect::loopsRemainingChanged() + \since 1.0 + + This handler is called when the remaining number of loops has changed. +*/ + +/*! + \qmlsignal SoundEffect::volumeChanged() + \since 1.0 + + This handler is called when the volume has changed. +*/ + +/*! + \qmlsignal SoundEffect::mutedChanged() + \since 1.0 + + This handler is called when the mute state has changed. +*/ + +/*! + \qmlsignal SoundEffect::playingChanged() + \since 1.0 + + This handler is called when the playing property has changed. +*/ + +/*! + \qmlsignal SoundEffect::statusChanged() + + This handler is called when the status property has changed. + \since 1.0 +*/ + + +/*! + \since 1.0 +*/ + +QSoundEffect::QSoundEffect(QObject *parent) : + QObject(parent) +{ + d = new QSoundEffectPrivate(this); + connect(d, SIGNAL(loopsRemainingChanged()), SIGNAL(loopsRemainingChanged())); + connect(d, SIGNAL(volumeChanged()), SIGNAL(volumeChanged())); + connect(d, SIGNAL(mutedChanged()), SIGNAL(mutedChanged())); + connect(d, SIGNAL(loadedChanged()), SIGNAL(loadedChanged())); + connect(d, SIGNAL(playingChanged()), SIGNAL(playingChanged())); + connect(d, SIGNAL(statusChanged()), SIGNAL(statusChanged())); +} + +QSoundEffect::~QSoundEffect() +{ + d->release(); +} + +QStringList QSoundEffect::supportedMimeTypes() +{ + return QSoundEffectPrivate::supportedMimeTypes(); +} + +QUrl QSoundEffect::source() const +{ + return d->source(); +} + +void QSoundEffect::setSource(const QUrl &url) +{ + if (d->source() == url) + return; + + d->setSource(url); + + emit sourceChanged(); +} + +int QSoundEffect::loopCount() const +{ + return d->loopCount(); +} + +int QSoundEffect::loopsRemaining() const +{ + return d->loopsRemaining(); +} + +void QSoundEffect::setLoopCount(int loopCount) +{ + if (loopCount < 0 && loopCount != Infinite) { + qWarning("SoundEffect: loops should be SoundEffect.Infinite, 0 or positive integer"); + return; + } + if (loopCount == 0) + loopCount = 1; + if (d->loopCount() == loopCount) + return; + + d->setLoopCount(loopCount); + emit loopCountChanged(); +} + +qreal QSoundEffect::volume() const +{ + return qreal(d->volume()) / 100; +} + +void QSoundEffect::setVolume(qreal volume) +{ + if (volume < 0 || volume > 1) { + qWarning("SoundEffect: volume should be between 0.0 and 1.0"); + return; + } + int iVolume = qRound(volume * 100); + if (d->volume() == iVolume) + return; + + d->setVolume(iVolume); +} + +bool QSoundEffect::isMuted() const +{ + return d->isMuted(); +} + +void QSoundEffect::setMuted(bool muted) +{ + if (d->isMuted() == muted) + return; + + d->setMuted(muted); +} + +bool QSoundEffect::isLoaded() const +{ + return d->isLoaded(); +} + +/*! + \qmlmethod SoundEffect::play() + + Start playback of the sound effect, looping the effect for the number of + times as specificed in the loops property. + + This is the default method for SoundEffect. + + \snippet doc/src/snippets/multimedia-snippets/soundeffect.qml play sound on click + \since 1.0 +*/ +void QSoundEffect::play() +{ + d->play(); +} + +bool QSoundEffect::isPlaying() const +{ + return d->isPlaying(); +} + +QSoundEffect::Status QSoundEffect::status() const +{ + return d->status(); +} + + +/*! + \qmlmethod SoundEffect::stop() + + Stop current playback. + Note that if the backend is PulseAudio, due to the limitation of the underlying API, + tis stop will only prevent next looping but will not be able to stop current playback immediately. + + \since 1.0 + */ +void QSoundEffect::stop() +{ + d->stop(); +} + +QT_END_NAMESPACE + +#include "moc_qsoundeffect.cpp" diff --git a/src/multimedia/audio/qsoundeffect.h b/src/multimedia/audio/qsoundeffect.h new file mode 100644 index 000000000..f5df8328e --- /dev/null +++ b/src/multimedia/audio/qsoundeffect.h @@ -0,0 +1,135 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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$ +** +****************************************************************************/ + +#ifndef QSOUNDEFFECT_H +#define QSOUNDEFFECT_H + +#include <qtmultimediadefs.h> +#include <QtCore/qobject.h> +#include <QtCore/qurl.h> +#include <QtCore/qstringlist.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + + +class QSoundEffectPrivate; + +class Q_MULTIMEDIA_EXPORT QSoundEffect : public QObject +{ + Q_OBJECT + Q_CLASSINFO("DefaultMethod", "play()") + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(int loops READ loopCount WRITE setLoopCount NOTIFY loopCountChanged) + Q_PROPERTY(int loopsRemaining READ loopsRemaining NOTIFY loopsRemainingChanged) + Q_PROPERTY(qreal volume READ volume WRITE setVolume NOTIFY volumeChanged) + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) + Q_PROPERTY(bool playing READ isPlaying NOTIFY playingChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_ENUMS(Loop) + Q_ENUMS(Status) + +public: + enum Loop + { + Infinite = -2, + }; + + enum Status + { + Null, + Loading, + Ready, + Error + }; + + explicit QSoundEffect(QObject *parent = 0); + ~QSoundEffect(); + + static QStringList supportedMimeTypes(); + + QUrl source() const; + void setSource(const QUrl &url); + + int loopCount() const; + int loopsRemaining() const; + void setLoopCount(int loopCount); + + qreal volume() const; + void setVolume(qreal volume); + + bool isMuted() const; + void setMuted(bool muted); + + bool isLoaded() const; + + bool isPlaying() const; + Status status() const; + +Q_SIGNALS: + void sourceChanged(); + void loopCountChanged(); + void loopsRemainingChanged(); + void volumeChanged(); + void mutedChanged(); + void loadedChanged(); + void playingChanged(); + void statusChanged(); + +public Q_SLOTS: + void play(); + void stop(); + +private: + Q_DISABLE_COPY(QSoundEffect) + QSoundEffectPrivate* d; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + + +#endif // QSOUNDEFFECT_H diff --git a/src/multimedia/audio/qsoundeffect_pulse_p.cpp b/src/multimedia/audio/qsoundeffect_pulse_p.cpp new file mode 100644 index 000000000..373586ef5 --- /dev/null +++ b/src/multimedia/audio/qsoundeffect_pulse_p.cpp @@ -0,0 +1,1100 @@ +/**************************************************************************** +** +** 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 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// INTERNAL USE ONLY: Do NOT use for any other purpose. +// + +#include <QtCore/qcoreapplication.h> +#include <qaudioformat.h> +#include <QtNetwork> +#include <QTime> + +#include "qsoundeffect_pulse_p.h" + +#if defined(Q_WS_MAEMO_6) +#include <pulse/ext-stream-restore.h> +#endif + +#include <unistd.h> + +//#define QT_PA_DEBUG +#ifndef QTM_PULSEAUDIO_DEFAULTBUFFER +#define QT_PA_STREAM_BUFFER_SIZE_MAX (1024 * 64) //64KB is a trade-off for balancing control latency and uploading overhead +#endif + +QT_BEGIN_NAMESPACE + +namespace +{ +inline pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format) +{ + pa_sample_spec spec; + + spec.rate = format.frequency(); + spec.channels = format.channels(); + + if (format.sampleSize() == 8) + spec.format = PA_SAMPLE_U8; + else if (format.sampleSize() == 16) { + switch (format.byteOrder()) { + case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S16BE; break; + case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S16LE; break; + } + } + else if (format.sampleSize() == 32) { + switch (format.byteOrder()) { + case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S32BE; break; + case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S32LE; break; + } + } + + return spec; +} + +class PulseDaemon : public QObject +{ + Q_OBJECT +public: + PulseDaemon(): m_prepared(false) + { + prepare(); + } + + ~PulseDaemon() + { + if (m_prepared) + release(); + } + + inline void lock() + { + pa_threaded_mainloop_lock(m_mainLoop); + } + + inline void unlock() + { + pa_threaded_mainloop_unlock(m_mainLoop); + } + + inline pa_context *context() const + { + return m_context; + } + + inline pa_cvolume * calcVolume(pa_cvolume *dest, int soundEffectVolume) + { + pa_volume_t v = m_vol * soundEffectVolume / 100; + for (int i = 0; i < dest->channels; ++i) + dest->values[i] = v; + return dest; + } + + void updateStatus(const pa_cvolume& volume) + { + if (m_vol != pa_cvolume_max(&volume)) { + m_vol = pa_cvolume_max(&volume); + emit volumeChanged(); + } + } + +Q_SIGNALS: + void contextReady(); + void volumeChanged(); + +private: + void prepare() + { + m_vol = PA_VOLUME_NORM; + + m_mainLoop = pa_threaded_mainloop_new(); + if (m_mainLoop == 0) { + qWarning("PulseAudioService: unable to create pulseaudio mainloop"); + return; + } + + if (pa_threaded_mainloop_start(m_mainLoop) != 0) { + qWarning("PulseAudioService: unable to start pulseaudio mainloop"); + pa_threaded_mainloop_free(m_mainLoop); + return; + } + + m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop); + + lock(); + m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtPulseAudio:%1")).arg(::getpid()).toAscii().constData()); + + pa_context_set_state_callback(m_context, context_state_callback, this); + + if (m_context == 0) { + qWarning("PulseAudioService: Unable to create new pulseaudio context"); + pa_threaded_mainloop_free(m_mainLoop); + return; + } + + if (pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0) < 0) { + qWarning("PulseAudioService: pa_context_connect() failed"); + pa_context_unref(m_context); + pa_threaded_mainloop_free(m_mainLoop); + return; + } + unlock(); + + m_prepared = true; + } + + void release() + { + if (!m_prepared) return; + pa_threaded_mainloop_stop(m_mainLoop); + pa_threaded_mainloop_free(m_mainLoop); + m_prepared = false; + } + + static void context_state_callback(pa_context *c, void *userdata) + { + PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata); + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + case PA_CONTEXT_READY: + #if defined(Q_WS_MAEMO_6) + pa_ext_stream_restore_read(c, &stream_restore_info_callback, self); + pa_ext_stream_restore_set_subscribe_cb(c, &stream_restore_monitor_callback, self); + pa_ext_stream_restore_subscribe(c, 1, 0, self); + #endif + QMetaObject::invokeMethod(self, "contextReady", Qt::QueuedConnection); + break; + default: + break; + } + } + +#if defined(Q_WS_MAEMO_6) + + static void stream_restore_monitor_callback(pa_context *c, void *userdata) + { + PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata); + pa_ext_stream_restore_read(c, &stream_restore_info_callback, self); + } + + static void stream_restore_info_callback(pa_context *c, + const pa_ext_stream_restore_info *info, + int eol, void *userdata) + { + Q_UNUSED(c) + + PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata); + + if (!eol) { + if (QString(info->name).startsWith(QLatin1String("sink-input-by-media-role:x-maemo"))) { +#ifdef QT_PA_DEBUG + qDebug() << "x-maemo volume =(" << info->volume.values[0] * 100 / PA_VOLUME_NORM << "," + << info->volume.values[1] * 100 / PA_VOLUME_NORM << "), " + << "mute = " << info->mute; +#endif + self->updateStatus(info->volume); + } + } + } +#endif + + pa_volume_t m_vol; + + bool m_prepared; + pa_context *m_context; + pa_threaded_mainloop *m_mainLoop; + pa_mainloop_api *m_mainLoopApi; +}; + +} + +Q_GLOBAL_STATIC(PulseDaemon, daemon) +Q_GLOBAL_STATIC(QSampleCache, sampleCache) + +namespace +{ +class PulseDaemonLocker +{ +public: + PulseDaemonLocker() + { + daemon()->lock(); + } + + ~PulseDaemonLocker() + { + daemon()->unlock(); + } +}; +} + +class QSoundEffectRef +{ +public: + QSoundEffectRef(QSoundEffectPrivate *target) + : m_ref(1) + , m_target(target) + { +#ifdef QT_PA_DEBUG + qDebug() << "QSoundEffectRef(" << this << ") ctor"; +#endif + } + + QSoundEffectRef *getRef() + { +#ifdef QT_PA_DEBUG + qDebug() << "QSoundEffectRef(" << this << ") getRef"; +#endif + QMutexLocker locker(&m_mutex); + m_ref++; + return this; + } + + void release() + { +#ifdef QT_PA_DEBUG + qDebug() << "QSoundEffectRef(" << this << ") Release"; +#endif + m_mutex.lock(); + --m_ref; + if (m_ref == 0) { + m_mutex.unlock(); +#ifdef QT_PA_DEBUG + qDebug() << "QSoundEffectRef(" << this << ") deleted"; +#endif + delete this; + return; + } + m_mutex.unlock(); + } + + QSoundEffectPrivate* soundEffect() const + { + QMutexLocker locker(&m_mutex); + return m_target; + } + + void notifyDeleted() + { +#ifdef QT_PA_DEBUG + qDebug() << "QSoundEffectRef(" << this << ") notifyDeleted"; +#endif + QMutexLocker locker(&m_mutex); + m_target = NULL; + } + +private: + int m_ref; + mutable QMutex m_mutex; + QSoundEffectPrivate *m_target; +}; + +QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent): + QObject(parent), + m_pulseStream(0), + m_sinkInputId(-1), + m_emptying(false), + m_sampleReady(false), + m_playing(false), + m_status(QSoundEffect::Null), + m_muted(false), + m_playQueued(false), + m_stopping(false), + m_volume(100), + m_loopCount(1), + m_runningCount(0), + m_sample(0) , + m_position(0) +{ + m_ref = new QSoundEffectRef(this); + pa_sample_spec_init(&m_pulseSpec); +} + +void QSoundEffectPrivate::release() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "release"; +#endif + m_ref->notifyDeleted(); + unloadPulseStream(); + if (m_sample) { + m_sample->release(); + m_sample = 0; + } + + this->deleteLater(); +} + +QSoundEffectPrivate::~QSoundEffectPrivate() +{ + m_ref->release(); +} + +QStringList QSoundEffectPrivate::supportedMimeTypes() +{ + QStringList supportedTypes; + supportedTypes << QLatin1String("audio/x-wav") << QLatin1String("audio/vnd.wave") ; + return supportedTypes; +} + +QUrl QSoundEffectPrivate::source() const +{ + return m_source; +} + +void QSoundEffectPrivate::setSource(const QUrl &url) +{ + Q_ASSERT(m_source != url); +#ifdef QT_PA_DEBUG + qDebug() << this << "setSource =" << url; +#endif + stop(); + if (m_sample) { + if (!m_sampleReady) { + disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError())); + disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady())); + } + m_sample->release(); + m_sample = 0; + } + + m_source = url; + m_sampleReady = false; + + PulseDaemonLocker locker; + setLoopsRemaining(0); + if (m_pulseStream && !pa_stream_is_corked(m_pulseStream)) { + pa_stream_set_write_callback(m_pulseStream, 0, 0); + pa_stream_set_underflow_callback(m_pulseStream, 0, 0); + pa_operation_unref(pa_stream_cork(m_pulseStream, 1, 0, 0)); + } + setPlaying(false); + + if (url.isEmpty()) { + setStatus(QSoundEffect::Null); + return; + } + + setStatus(QSoundEffect::Loading); + m_sample = sampleCache()->requestSample(url); + connect(m_sample, SIGNAL(error()), this, SLOT(decoderError())); + connect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady())); + switch(m_sample->state()) { + case QSample::Ready: + sampleReady(); + break; + case QSample::Error: + decoderError(); + break; + default: + break; + } +} + +int QSoundEffectPrivate::loopCount() const +{ + return m_loopCount; +} + +int QSoundEffectPrivate::loopsRemaining() const +{ + return m_runningCount; +} + +void QSoundEffectPrivate::setLoopCount(int loopCount) +{ + if (loopCount == 0) + loopCount = 1; + m_loopCount = loopCount; +} + +int QSoundEffectPrivate::volume() const +{ + return m_volume; +} + +void QSoundEffectPrivate::setVolume(int volume) +{ + m_volume = volume; + emit volumeChanged(); + updateVolume(); +} + +void QSoundEffectPrivate::updateVolume() +{ + if (m_sinkInputId < 0) + return; + PulseDaemonLocker locker; + pa_cvolume volume; + volume.channels = m_pulseSpec.channels; + pa_operation_unref(pa_context_set_sink_input_volume(daemon()->context(), m_sinkInputId, daemon()->calcVolume(&volume, m_volume), setvolume_callback, m_ref->getRef())); + Q_ASSERT(pa_cvolume_valid(&volume)); +#ifdef QT_PA_DEBUG + qDebug() << this << "updateVolume =" << pa_cvolume_max(&volume); +#endif +} + +bool QSoundEffectPrivate::isMuted() const +{ + return m_muted; +} + +void QSoundEffectPrivate::setMuted(bool muted) +{ + m_muted = muted; + emit mutedChanged(); + updateMuted(); +} + +void QSoundEffectPrivate::updateMuted() +{ + if (m_sinkInputId < 0) + return; + PulseDaemonLocker locker; + pa_operation_unref(pa_context_set_sink_input_mute(daemon()->context(), m_sinkInputId, m_muted, setmuted_callback, m_ref->getRef())); +#ifdef QT_PA_DEBUG + qDebug() << this << "updateMuted = " << m_muted; +#endif +} + +bool QSoundEffectPrivate::isLoaded() const +{ + return m_status == QSoundEffect::Ready; +} + +bool QSoundEffectPrivate::isPlaying() const +{ + return m_playing; +} + +QSoundEffect::Status QSoundEffectPrivate::status() const +{ + return m_status; +} + +void QSoundEffectPrivate::setPlaying(bool playing) +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "setPlaying(" << playing << ")"; +#endif + if (m_playing == playing) + return; + if (!playing) + m_playQueued = false; + m_playing = playing; + emit playingChanged(); +} + +void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "setStatus" << status; +#endif + if (m_status == status) + return; + bool oldLoaded = isLoaded(); + m_status = status; + emit statusChanged(); + if (oldLoaded != isLoaded()) + emit loadedChanged(); +} + +void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining) +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "setLoopsRemaining " << loopsRemaining; +#endif + if (m_runningCount == loopsRemaining) + return; + m_runningCount = loopsRemaining; + emit loopsRemainingChanged(); +} + +void QSoundEffectPrivate::play() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "play"; +#endif + if (m_status == QSoundEffect::Null || m_status == QSoundEffect::Error || m_playQueued) + return; + + PulseDaemonLocker locker; + if (!m_sampleReady || !m_pulseStream || m_stopping || m_emptying) { +#ifdef QT_PA_DEBUG + qDebug() << this << "play deferred"; +#endif + m_playQueued = true; + } else { + if (m_playing) { //restart playing from the beginning +#ifdef QT_PA_DEBUG + qDebug() << this << "restart playing"; +#endif + setLoopsRemaining(0); + m_playQueued = true; + Q_ASSERT(m_pulseStream); + emptyStream(); + return; + } + m_runningCount = m_loopCount; + playSample(); + } + + setPlaying(true); +} + +void QSoundEffectPrivate::emptyStream() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "emptyStream"; +#endif + m_emptying = true; + pa_stream_set_write_callback(m_pulseStream, 0, 0); + pa_stream_set_underflow_callback(m_pulseStream, 0, 0); + pa_operation_unref(pa_stream_flush(m_pulseStream, stream_flush_callback, m_ref->getRef())); +} + +void QSoundEffectPrivate::emptyComplete() +{ + PulseDaemonLocker locker; + m_emptying = false; + pa_operation_unref(pa_stream_cork(m_pulseStream, 1, stream_cork_callback, m_ref->getRef())); +} + +void QSoundEffectPrivate::sampleReady() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "sampleReady"; +#endif + disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError())); + disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady())); + pa_sample_spec newFormatSpec = audioFormatToSampleSpec(m_sample->format()); + + if (m_pulseStream && (memcmp(&m_pulseSpec, &newFormatSpec, sizeof(m_pulseSpec)) != 0)) { + unloadPulseStream(); + } + m_pulseSpec = newFormatSpec; + + m_sampleReady = true; + m_position = 0; + + if (m_name.isNull()) + m_name = QString(QLatin1String("QtPulseSample-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8(); + + PulseDaemonLocker locker; + if (m_pulseStream) { +#ifdef QT_PA_DEBUG + qDebug() << this << "reuse existing pulsestream"; +#endif +#ifdef QTM_PULSEAUDIO_DEFAULTBUFFER + const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(m_pulseStream); + if (bufferAttr->prebuf > uint32_t(m_sample->data().size())) { + pa_buffer_attr newBufferAttr; + newBufferAttr = *bufferAttr; + newBufferAttr.prebuf = m_sample->data().size(); + pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, m_ref->getRef())); + } else { + streamReady(); + } +#else + const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(m_pulseStream); + if (bufferAttr->tlength < m_sample->data().size() && bufferAttr->tlength < QT_PA_STREAM_BUFFER_SIZE_MAX) { + pa_buffer_attr newBufferAttr; + newBufferAttr.maxlength = -1; + newBufferAttr.tlength = qMin(m_sample->data().size(), QT_PA_STREAM_BUFFER_SIZE_MAX); + newBufferAttr.minreq = bufferAttr->tlength / 2; + newBufferAttr.prebuf = -1; + newBufferAttr.fragsize = -1; + pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_reset_buffer_callback, m_ref->getRef())); + } else if (bufferAttr->prebuf > uint32_t(m_sample->data().size())) { + pa_buffer_attr newBufferAttr; + newBufferAttr = *bufferAttr; + newBufferAttr.prebuf = m_sample->data().size(); + pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, m_ref->getRef())); + } else { + streamReady(); + } +#endif + } else { + if (pa_context_get_state(daemon()->context()) != PA_CONTEXT_READY) { + connect(daemon(), SIGNAL(contextReady()), SLOT(contextReady())); + return; + } + createPulseStream(); + } +} + +void QSoundEffectPrivate::decoderError() +{ + qWarning("QSoundEffect(pulseaudio): Error decoding source"); + disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError())); + bool playingDirty = false; + if (m_playing) { + m_playing = false; + playingDirty = true; + } + setStatus(QSoundEffect::Error); + if (playingDirty) + emit playingChanged(); +} + +void QSoundEffectPrivate::unloadPulseStream() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "unloadPulseStream"; +#endif + m_sinkInputId = -1; + PulseDaemonLocker locker; + if (m_pulseStream) { + pa_stream_set_state_callback(m_pulseStream, 0, 0); + pa_stream_set_write_callback(m_pulseStream, 0, 0); + pa_stream_set_underflow_callback(m_pulseStream, 0, 0); + pa_stream_disconnect(m_pulseStream); + pa_stream_unref(m_pulseStream); + disconnect(daemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume())); + m_pulseStream = 0; + } +} + +void QSoundEffectPrivate::prepare() +{ + if (!m_pulseStream || !m_sampleReady) + return; + PulseDaemonLocker locker; + pa_stream_set_write_callback(m_pulseStream, stream_write_callback, this); + pa_stream_set_underflow_callback(m_pulseStream, stream_underrun_callback, this); + m_stopping = false; + size_t writeBytes = size_t(qMin(m_pulseBufferSize, m_sample->data().size())); +#ifdef QT_PA_DEBUG + qDebug() << this << "prepare(): writable size =" << pa_stream_writable_size(m_pulseStream) + << "actual writeBytes =" << writeBytes + << "m_playQueued =" << m_playQueued; +#endif + m_position = int(writeBytes); + if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data())), writeBytes, + stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) { + qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(daemon()->context()))); + } + if (m_playQueued) { + m_playQueued = false; + setLoopsRemaining(m_loopCount); + playSample(); + } +} + +void QSoundEffectPrivate::uploadSample() +{ + if (m_runningCount == 0) { +#ifdef QT_PA_DEBUG + qDebug() << this << "uploadSample: return due to 0 m_runningCount"; +#endif + return; + } +#ifdef QT_PA_DEBUG + qDebug() << this << "uploadSample: m_runningCount =" << m_runningCount; +#endif + if (m_position == m_sample->data().size()) { + m_position = 0; + if (m_runningCount > 0) + setLoopsRemaining(m_runningCount - 1); + if (m_runningCount == 0) { + return; + } + } + + int writtenBytes = 0; + int writableSize = int(pa_stream_writable_size(m_pulseStream)); + int firstPartLength = qMin(m_sample->data().size() - m_position, writableSize); + if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data()) + m_position), + firstPartLength, stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) { + qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(daemon()->context()))); + } + writtenBytes = firstPartLength; + m_position += firstPartLength; + if (m_position == m_sample->data().size()) { + m_position = 0; + if (m_runningCount > 0) + setLoopsRemaining(m_runningCount - 1); + if (m_runningCount != 0 && firstPartLength < writableSize) + { + while (writtenBytes < writableSize) { + int writeSize = qMin(writableSize - writtenBytes, m_sample->data().size()); + if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data())), + writeSize, stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) { + qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(daemon()->context()))); + } + writtenBytes += writeSize; + if (writeSize < m_sample->data().size()) { + m_position = writeSize; + break; + } + if (m_runningCount > 0) + setLoopsRemaining(m_runningCount - 1); + if (m_runningCount == 0) + break; + } + } + } +#ifdef QT_PA_DEBUG + qDebug() << this << "uploadSample: use direct write, writeable size =" << writableSize + << "actual writtenBytes =" << writtenBytes; +#endif +} + +void QSoundEffectPrivate::playSample() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "playSample"; +#endif + Q_ASSERT(m_pulseStream); + pa_operation_unref(pa_stream_cork(m_pulseStream, 0, 0, 0)); +} + +void QSoundEffectPrivate::stop() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "stop"; +#endif + if (!m_playing) + return; + setPlaying(false); + PulseDaemonLocker locker; + m_stopping = true; + if (m_pulseStream) + emptyStream(); + setLoopsRemaining(0); + m_position = 0; + m_playQueued = false; +} + +void QSoundEffectPrivate::underRun() +{ + stop(); +} + +void QSoundEffectPrivate::streamReady() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "streamReady"; +#endif + PulseDaemonLocker locker; + m_sinkInputId = pa_stream_get_index(m_pulseStream); + updateMuted(); + updateVolume(); +#ifdef QT_PA_DEBUG + const pa_buffer_attr *realBufAttr = pa_stream_get_buffer_attr(m_pulseStream); + qDebug() << this << "m_sinkInputId =" << m_sinkInputId + << "tlength =" << realBufAttr->tlength << "maxlength =" << realBufAttr->maxlength + << "minreq = " << realBufAttr->minreq << "prebuf =" << realBufAttr->prebuf; +#endif + prepare(); + setStatus(QSoundEffect::Ready); +} + +void QSoundEffectPrivate::createPulseStream() +{ +#ifdef QT_PA_DEBUG + qDebug() << this << "createPulseStream"; +#endif + + pa_proplist *propList = pa_proplist_new(); + pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, "soundeffect"); + pa_stream *stream = pa_stream_new_with_proplist(daemon()->context(), m_name.constData(), &m_pulseSpec, 0, propList); + pa_proplist_free(propList); + + connect(daemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume())); + + if (stream == 0) { + qWarning("QSoundEffect(pulseaudio): Failed to create stream"); + m_pulseStream = 0; + setStatus(QSoundEffect::Error); + setPlaying(false); + return; + } + else { + pa_stream_set_state_callback(stream, stream_state_callback, this); + pa_stream_set_write_callback(stream, stream_write_callback, this); + pa_stream_set_underflow_callback(stream, stream_underrun_callback, this); + } + m_pulseStream = stream; + +#ifndef QTM_PULSEAUDIO_DEFAULTBUFFER + pa_buffer_attr bufferAttr; + bufferAttr.tlength = qMin(m_sample->data().size(), QT_PA_STREAM_BUFFER_SIZE_MAX); + bufferAttr.maxlength = -1; + bufferAttr.minreq = bufferAttr.tlength / 2; + bufferAttr.prebuf = -1; + bufferAttr.fragsize = -1; + if (pa_stream_connect_playback(m_pulseStream, 0, &bufferAttr, +#else + if (pa_stream_connect_playback(m_pulseStream, 0, 0, +#endif + m_muted ? pa_stream_flags_t(PA_STREAM_START_MUTED | PA_STREAM_START_CORKED) + : pa_stream_flags_t(PA_STREAM_START_UNMUTED | PA_STREAM_START_CORKED), + 0, 0) < 0) { + qWarning("QSoundEffect(pulseaudio): Failed to connect stream, error = %s", + pa_strerror(pa_context_errno(daemon()->context()))); + } +} + +void QSoundEffectPrivate::contextReady() +{ + disconnect(daemon(), SIGNAL(contextReady()), this, SLOT(contextReady())); + PulseDaemonLocker locker; + createPulseStream(); +} + +void QSoundEffectPrivate::stream_write_callback(pa_stream *s, size_t length, void *userdata) +{ + Q_UNUSED(length); + Q_UNUSED(s); + + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_write_callback"; +#endif + self->uploadSample(); +} + +void QSoundEffectPrivate::stream_state_callback(pa_stream *s, void *userdata) +{ + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); + switch (pa_stream_get_state(s)) { + case PA_STREAM_READY: + { +#ifdef QT_PA_DEBUG + qDebug() << self << "pulse stream ready"; +#endif + const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(self->m_pulseStream); + self->m_pulseBufferSize = bufferAttr->tlength; + if (bufferAttr->prebuf > uint32_t(self->m_sample->data().size())) { + pa_buffer_attr newBufferAttr; + newBufferAttr = *bufferAttr; + newBufferAttr.prebuf = self->m_sample->data().size(); + pa_stream_set_buffer_attr(self->m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, self->m_ref->getRef()); + } else { + QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection); + } + break; + } + case PA_STREAM_CREATING: +#ifdef QT_PA_DEBUG + qDebug() << self << "pulse stream creating"; +#endif + break; + case PA_STREAM_TERMINATED: +#ifdef QT_PA_DEBUG + qDebug() << self << "pulse stream terminated"; +#endif + break; + + case PA_STREAM_FAILED: + default: + qWarning("QSoundEffect(pulseaudio): Error in pulse audio stream"); + break; + } +} + +void QSoundEffectPrivate::stream_reset_buffer_callback(pa_stream *s, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "stream_reset_buffer_callback"; +#endif + Q_UNUSED(s); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; + + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to reset buffer attribute"); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_reset_buffer_callback"; +#endif + const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(self->m_pulseStream); + self->m_pulseBufferSize = bufferAttr->tlength; + if (bufferAttr->prebuf > uint32_t(self->m_sample->data().size())) { + pa_buffer_attr newBufferAttr; + newBufferAttr = *bufferAttr; + newBufferAttr.prebuf = self->m_sample->data().size(); + pa_stream_set_buffer_attr(self->m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, userdata); + } else { + QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection); + } +} + +void QSoundEffectPrivate::stream_adjust_prebuffer_callback(pa_stream *s, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "stream_adjust_prebuffer_callback"; +#endif + Q_UNUSED(s); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; + + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to adjust pre-buffer attribute"); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_adjust_prebuffer_callback"; +#endif + QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection); +} + +void QSoundEffectPrivate::setvolume_callback(pa_context *c, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "setvolume_callback"; +#endif + Q_UNUSED(c); + Q_UNUSED(userdata); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; +#ifdef QT_PA_DEBUG + qDebug() << self << "setvolume_callback"; +#endif + if (!success) { + qWarning("QSoundEffect(pulseaudio): faild to set volume"); + } +} + +void QSoundEffectPrivate::setmuted_callback(pa_context *c, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "setmuted_callback"; +#endif + Q_UNUSED(c); + Q_UNUSED(userdata); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; +#ifdef QT_PA_DEBUG + qDebug() << self << "setmuted_callback"; +#endif + if (!success) { + qWarning("QSoundEffect(pulseaudio): faild to set muted"); + } +} + +void QSoundEffectPrivate::stream_underrun_callback(pa_stream *s, void *userdata) +{ + Q_UNUSED(s); + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_underrun_callback"; +#endif + if (self->m_runningCount == 0 && !self->m_playQueued) + QMetaObject::invokeMethod(self, "underRun", Qt::QueuedConnection); +#ifdef QT_PA_DEBUG + else + qDebug() << "underun corked =" << pa_stream_is_corked(s); +#endif +} + +void QSoundEffectPrivate::stream_cork_callback(pa_stream *s, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "stream_cork_callback"; +#endif + Q_UNUSED(s); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; + + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to stop"); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_cork_callback"; +#endif + QMetaObject::invokeMethod(self, "prepare", Qt::QueuedConnection); +} + +void QSoundEffectPrivate::stream_flush_callback(pa_stream *s, int success, void *userdata) +{ +#ifdef QT_PA_DEBUG + qDebug() << "stream_flush_callback"; +#endif + Q_UNUSED(s); + QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata); + QSoundEffectPrivate *self = ref->soundEffect(); + ref->release(); + if (!self) + return; + + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to drain"); +#ifdef QT_PA_DEBUG + qDebug() << self << "stream_flush_callback"; +#endif + QMetaObject::invokeMethod(self, "emptyComplete", Qt::QueuedConnection); +} + +void QSoundEffectPrivate::stream_write_done_callback(void *p) +{ + Q_UNUSED(p); +#ifdef QT_PA_DEBUG + qDebug() << "stream_write_done_callback"; +#endif +} + +QT_END_NAMESPACE + +#include "moc_qsoundeffect_pulse_p.cpp" +#include "qsoundeffect_pulse_p.moc" diff --git a/src/multimedia/audio/qsoundeffect_pulse_p.h b/src/multimedia/audio/qsoundeffect_pulse_p.h new file mode 100644 index 000000000..35b586e8d --- /dev/null +++ b/src/multimedia/audio/qsoundeffect_pulse_p.h @@ -0,0 +1,170 @@ +/**************************************************************************** +** +** 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 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$ +** +****************************************************************************/ + +#ifndef QSOUNDEFFECT_PULSE_H +#define QSOUNDEFFECT_PULSE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + +#include "qsoundeffect.h" + +#include <QtCore/qobject.h> +#include <QtCore/qdatetime.h> +#include <qmediaplayer.h> +#include <pulse/pulseaudio.h> +#include "qsamplecache_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + +class QSoundEffectRef; + +class QSoundEffectPrivate : public QObject +{ + Q_OBJECT +public: + explicit QSoundEffectPrivate(QObject* parent); + ~QSoundEffectPrivate(); + + static QStringList supportedMimeTypes(); + + QUrl source() const; + void setSource(const QUrl &url); + int loopCount() const; + int loopsRemaining() const; + void setLoopCount(int loopCount); + int volume() const; + void setVolume(int volume); + bool isMuted() const; + void setMuted(bool muted); + bool isLoaded() const; + bool isPlaying() const; + QSoundEffect::Status status() const; + + void release(); + +public Q_SLOTS: + void play(); + void stop(); + +Q_SIGNALS: + void loopsRemainingChanged(); + void volumeChanged(); + void mutedChanged(); + void loadedChanged(); + void playingChanged(); + void statusChanged(); + +private Q_SLOTS: + void decoderError(); + void sampleReady(); + void uploadSample(); + void contextReady(); + void underRun(); + void prepare(); + void streamReady(); + void emptyComplete(); + void updateVolume(); + void updateMuted(); + +private: + void playSample(); + + void emptyStream(); + void createPulseStream(); + void unloadPulseStream(); + + void setPlaying(bool playing); + void setStatus(QSoundEffect::Status status); + void setLoopsRemaining(int loopsRemaining); + + static void stream_write_callback(pa_stream *s, size_t length, void *userdata); + static void stream_state_callback(pa_stream *s, void *userdata); + static void stream_underrun_callback(pa_stream *s, void *userdata); + static void stream_cork_callback(pa_stream *s, int success, void *userdata); + static void stream_flush_callback(pa_stream *s, int success, void *userdata); + static void stream_write_done_callback(void *p); + static void stream_adjust_prebuffer_callback(pa_stream *s, int success, void *userdata); + static void stream_reset_buffer_callback(pa_stream *s, int success, void *userdata); + static void setvolume_callback(pa_context *c, int success, void *userdata); + static void setmuted_callback(pa_context *c, int success, void *userdata); + + pa_stream *m_pulseStream; + int m_sinkInputId; + pa_sample_spec m_pulseSpec; + int m_pulseBufferSize; + + bool m_emptying; + bool m_sampleReady; + bool m_playing; + QSoundEffect::Status m_status; + bool m_muted; + bool m_playQueued; + bool m_stopping; + int m_volume; + int m_loopCount; + int m_runningCount; + QUrl m_source; + QByteArray m_name; + + QSample *m_sample; + int m_position; + QSoundEffectRef *m_ref; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSOUNDEFFECT_PULSE_H diff --git a/src/multimedia/audio/qsoundeffect_qmedia_p.cpp b/src/multimedia/audio/qsoundeffect_qmedia_p.cpp new file mode 100644 index 000000000..511aa06b5 --- /dev/null +++ b/src/multimedia/audio/qsoundeffect_qmedia_p.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** 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 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$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// INTERNAL USE ONLY: Do NOT use for any other purpose. +// + +#include "qsoundeffect_qmedia_p.h" + +#include <QtCore/qcoreapplication.h> + +#include "qmediacontent.h" +#include "qmediaplayer.h" + + +QT_BEGIN_NAMESPACE + +QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent): + QObject(parent), + m_loopCount(1), + m_runningCount(0), + m_playing(false), + m_status(QSoundEffect::Null), + m_player(0) +{ + m_player = new QMediaPlayer(this, QMediaPlayer::LowLatency); + connect(m_player, SIGNAL(stateChanged(QMediaPlayer::State)), SLOT(stateChanged(QMediaPlayer::State))); + connect(m_player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), SLOT(mediaStatusChanged(QMediaPlayer::MediaStatus))); + connect(m_player, SIGNAL(error(QMediaPlayer::Error)), SLOT(error(QMediaPlayer::Error))); + connect(m_player, SIGNAL(mutedChanged(bool)), SIGNAL(mutedChanged())); + connect(m_player, SIGNAL(volumeChanged(int)), SIGNAL(volumeChanged())); +} + +void QSoundEffectPrivate::release() +{ + this->deleteLater(); +} + +QSoundEffectPrivate::~QSoundEffectPrivate() +{ +} + +QStringList QSoundEffectPrivate::supportedMimeTypes() +{ + return QMediaPlayer::supportedMimeTypes(); +} + +QUrl QSoundEffectPrivate::source() const +{ + return m_player->media().canonicalUrl(); +} + +void QSoundEffectPrivate::setSource(const QUrl &url) +{ + m_player->setMedia(url); +} + +int QSoundEffectPrivate::loopCount() const +{ + return m_loopCount; +} + +int QSoundEffectPrivate::loopsRemaining() const +{ + return m_runningCount; +} + +void QSoundEffectPrivate::setLoopCount(int loopCount) +{ + m_loopCount = loopCount; +} + +int QSoundEffectPrivate::volume() const +{ + return m_player->volume(); +} + +void QSoundEffectPrivate::setVolume(int volume) +{ + m_player->setVolume(volume); +} + +bool QSoundEffectPrivate::isMuted() const +{ + return m_player->isMuted(); +} + +void QSoundEffectPrivate::setMuted(bool muted) +{ + m_player->setMuted(muted); +} + +bool QSoundEffectPrivate::isLoaded() const +{ + return m_status == QSoundEffect::Ready; +} + +bool QSoundEffectPrivate::isPlaying() const +{ + return m_playing; +} + +QSoundEffect::Status QSoundEffectPrivate::status() const +{ + return m_status; +} + +void QSoundEffectPrivate::play() +{ + if (m_status == QSoundEffect::Null || m_status == QSoundEffect::Error) + return; + if (m_loopCount < 0) { + setLoopsRemaining(-1); + } + else { + if (m_runningCount < 0) + setLoopsRemaining(0); + setLoopsRemaining(m_runningCount + m_loopCount); + } + m_player->play(); +} + +void QSoundEffectPrivate::stop() +{ + setLoopsRemaining(0); + m_player->stop(); +} + +void QSoundEffectPrivate::stateChanged(QMediaPlayer::State state) +{ + if (state == QMediaPlayer::StoppedState) { + if (m_runningCount < 0) { + m_player->play(); + } else if (m_runningCount == 0) { + setPlaying(false); + return; + } else { + setLoopsRemaining(m_runningCount - 1); + if (m_runningCount > 0) { + m_player->play(); + } else { + setPlaying(false); + } + } + } else { + setPlaying(true); + } +} + +void QSoundEffectPrivate::mediaStatusChanged(QMediaPlayer::MediaStatus status) +{ + switch(status) { + case QMediaPlayer::LoadingMedia: + setStatus(QSoundEffect::Loading); + break; + case QMediaPlayer::NoMedia: + setStatus(QSoundEffect::Null); + break; + case QMediaPlayer::InvalidMedia: + setStatus(QSoundEffect::Error); + break; + default: + setStatus(QSoundEffect::Ready); + break; + } +} + +void QSoundEffectPrivate::error(QMediaPlayer::Error err) +{ + bool playingDirty = false; + if (m_playing) { + m_playing = false; + playingDirty = true; + } + setStatus(QSoundEffect::Error); + if (playingDirty) + emit playingChanged(); +} + +void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) +{ + if (m_status == status) + return; + bool oldLoaded = isLoaded(); + m_status = status; + emit statusChanged(); + if (oldLoaded != isLoaded()) + emit loadedChanged(); +} + +void QSoundEffectPrivate::setPlaying(bool playing) +{ + if (m_playing == playing) + return; + m_playing = playing; + emit playingChanged(); +} + +void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining) +{ + if (m_runningCount == loopsRemaining) + return; + m_runningCount = loopsRemaining; + emit loopsRemainingChanged(); +} + +QT_END_NAMESPACE + +#include "moc_qsoundeffect_qmedia_p.cpp" diff --git a/src/multimedia/audio/qsoundeffect_qmedia_p.h b/src/multimedia/audio/qsoundeffect_qmedia_p.h new file mode 100644 index 000000000..bb91e50b0 --- /dev/null +++ b/src/multimedia/audio/qsoundeffect_qmedia_p.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt 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$ +** +****************************************************************************/ + +#ifndef QSOUNDEFFECT_QMEDIA_H +#define QSOUNDEFFECT_QMEDIA_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qobject.h> +#include <QtCore/qurl.h> +#include "qmediaplayer.h" +#include "qsoundeffect.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + + + +class QSoundEffectPrivate : public QObject +{ + Q_OBJECT +public: + + explicit QSoundEffectPrivate(QObject* parent); + ~QSoundEffectPrivate(); + + static QStringList supportedMimeTypes(); + + QUrl source() const; + void setSource(const QUrl &url); + int loopCount() const; + int loopsRemaining() const; + void setLoopCount(int loopCount); + int volume() const; + void setVolume(int volume); + bool isMuted() const; + void setMuted(bool muted); + bool isLoaded() const; + bool isPlaying() const; + QSoundEffect::Status status() const; + + void release(); + +public Q_SLOTS: + void play(); + void stop(); + +Q_SIGNALS: + void loopsRemainingChanged(); + void volumeChanged(); + void mutedChanged(); + void loadedChanged(); + void playingChanged(); + void statusChanged(); + +private Q_SLOTS: + void stateChanged(QMediaPlayer::State); + void mediaStatusChanged(QMediaPlayer::MediaStatus); + void error(QMediaPlayer::Error); + +private: + void setStatus(QSoundEffect::Status status); + void setPlaying(bool playing); + void setLoopsRemaining(int loopsRemaining); + + int m_loopCount; + int m_runningCount; + bool m_playing; + QSoundEffect::Status m_status; + QMediaPlayer *m_player; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSOUNDEFFECT_QMEDIA_H diff --git a/src/multimedia/audio/qwavedecoder_p.cpp b/src/multimedia/audio/qwavedecoder_p.cpp new file mode 100644 index 000000000..eb65a3aca --- /dev/null +++ b/src/multimedia/audio/qwavedecoder_p.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** +** 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 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 "qwavedecoder_p.h" + +#include <QtCore/qtimer.h> +#include <QtCore/qendian.h> + +QT_BEGIN_NAMESPACE + +QWaveDecoder::QWaveDecoder(QIODevice *s, QObject *parent): + QIODevice(parent), + haveFormat(false), + dataSize(0), + source(s), + state(QWaveDecoder::InitialState), + junkToSkip(0), + bigEndian(false) +{ + open(QIODevice::ReadOnly | QIODevice::Unbuffered); + + if (enoughDataAvailable()) + QTimer::singleShot(0, this, SLOT(handleData())); + else + connect(source, SIGNAL(readyRead()), SLOT(handleData())); +} + +QWaveDecoder::~QWaveDecoder() +{ +} + +QAudioFormat QWaveDecoder::audioFormat() const +{ + return format; +} + +int QWaveDecoder::duration() const +{ + return size() * 1000 / (format.sampleSize() / 8) / format.channels() / format.frequency(); +} + +qint64 QWaveDecoder::size() const +{ + return haveFormat ? dataSize : 0; +} + +bool QWaveDecoder::isSequential() const +{ + return source->isSequential(); +} + +qint64 QWaveDecoder::bytesAvailable() const +{ + return haveFormat ? source->bytesAvailable() : 0; +} + +qint64 QWaveDecoder::readData(char *data, qint64 maxlen) +{ + return haveFormat ? source->read(data, maxlen) : 0; +} + +qint64 QWaveDecoder::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return -1; +} + +void QWaveDecoder::parsingFailed() +{ + Q_ASSERT(source); + source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + emit parsingError(); +} + +void QWaveDecoder::handleData() +{ + // As a special "state", if we have junk to skip, we do + if (junkToSkip > 0) { + discardBytes(junkToSkip); // this also updates junkToSkip + + // If we couldn't skip all the junk, return + if (junkToSkip > 0) { + // We might have run out + if (source->atEnd()) + parsingFailed(); + return; + } + } + + if (state == QWaveDecoder::InitialState) { + if (source->bytesAvailable() < qint64(sizeof(RIFFHeader))) + return; + + RIFFHeader riff; + source->read(reinterpret_cast<char *>(&riff), sizeof(RIFFHeader)); + + // RIFF = little endian RIFF, RIFX = big endian RIFF + if (((qstrncmp(riff.descriptor.id, "RIFF", 4) != 0) && (qstrncmp(riff.descriptor.id, "RIFX", 4) != 0)) + || qstrncmp(riff.type, "WAVE", 4) != 0) { + parsingFailed(); + return; + } else { + state = QWaveDecoder::WaitingForFormatState; + if (qstrncmp(riff.descriptor.id, "RIFX", 4) == 0) + bigEndian = true; + else + bigEndian = false; + } + } + + if (state == QWaveDecoder::WaitingForFormatState) { + if (findChunk("fmt ")) { + chunk descriptor; + peekChunk(&descriptor); + + if (source->bytesAvailable() < qint64(descriptor.size + sizeof(chunk))) + return; + + WAVEHeader wave; + source->read(reinterpret_cast<char *>(&wave), sizeof(WAVEHeader)); + if (descriptor.size > sizeof(WAVEHeader)) + discardBytes(descriptor.size - sizeof(WAVEHeader)); + + // Swizzle this + if (bigEndian) { + wave.audioFormat = qFromBigEndian<quint16>(wave.audioFormat); + } + + if (wave.audioFormat != 0 && wave.audioFormat != 1) { + // 32bit wave files have format == 0xFFFE (WAVE_FORMAT_EXTENSIBLE). + // but don't support them at the moment. + parsingFailed(); + return; + } else { + format.setCodec(QLatin1String("audio/pcm")); + + if (bigEndian) { + int bps = qFromBigEndian<quint16>(wave.bitsPerSample); + + format.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt); + format.setByteOrder(QAudioFormat::BigEndian); + format.setFrequency(qFromBigEndian<quint32>(wave.sampleRate)); + format.setSampleSize(bps); + format.setChannels(qFromBigEndian<quint16>(wave.numChannels)); + } else { + int bps = qFromLittleEndian<quint16>(wave.bitsPerSample); + + format.setSampleType(bps == 8 ? QAudioFormat::UnSignedInt : QAudioFormat::SignedInt); + format.setByteOrder(QAudioFormat::LittleEndian); + format.setFrequency(qFromLittleEndian<quint32>(wave.sampleRate)); + format.setSampleSize(bps); + format.setChannels(qFromLittleEndian<quint16>(wave.numChannels)); + } + + state = QWaveDecoder::WaitingForDataState; + } + } + } + + if (state == QWaveDecoder::WaitingForDataState) { + if (findChunk("data")) { + source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + + chunk descriptor; + source->read(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); + if (bigEndian) + descriptor.size = qFromBigEndian<quint32>(descriptor.size); + + dataSize = descriptor.size; + + haveFormat = true; + connect(source, SIGNAL(readyRead()), SIGNAL(readyRead())); + emit formatKnown(); + + return; + } + } + + // If we hit the end without finding data, it's a parsing error + if (source->atEnd()) { + parsingFailed(); + } +} + +bool QWaveDecoder::enoughDataAvailable() +{ + chunk descriptor; + if (!peekChunk(&descriptor)) + return false; + + // This is only called for the RIFF/RIFX header, before bigEndian is set, + // so we have to manually swizzle + if (qstrncmp(descriptor.id, "RIFX", 4) == 0) + descriptor.size = qFromBigEndian<quint32>(descriptor.size); + + if (source->bytesAvailable() < qint64(sizeof(chunk) + descriptor.size)) + return false; + + return true; +} + +bool QWaveDecoder::findChunk(const char *chunkId) +{ + chunk descriptor; + if (!peekChunk(&descriptor)) + return false; + + if (qstrncmp(descriptor.id, chunkId, 4) == 0) + return true; + + // It's possible that bytes->available() is less than the chunk size + // if it's corrupt. + junkToSkip = qint64(sizeof(chunk) + descriptor.size); + while (source->bytesAvailable() > 0) { + // Skip the current amount + if (junkToSkip > 0) + discardBytes(junkToSkip); + + // If we still have stuff left, just exit and try again later + // since we can't call peekChunk + if (junkToSkip > 0) + return false; + + if (!peekChunk(&descriptor)) + return false; + + if (qstrncmp(descriptor.id, chunkId, 4) == 0) + return true; + } + + return false; +} + +// Handles endianness +bool QWaveDecoder::peekChunk(chunk *pChunk) +{ + if (source->bytesAvailable() < qint64(sizeof(chunk))) + return false; + + source->peek(reinterpret_cast<char *>(pChunk), sizeof(chunk)); + if (bigEndian) + pChunk->size = qFromBigEndian<quint32>(pChunk->size); + + return true; +} + +void QWaveDecoder::discardBytes(qint64 numBytes) +{ + // Discards a number of bytes + // If the iodevice doesn't have this many bytes in it, + // remember how much more junk we have to skip. + if (source->isSequential()) { + QByteArray r = source->read(qMin(numBytes, qint64(16384))); // uggh, wasted memory, limit to a max of 16k + if (r.size() < numBytes) + junkToSkip = numBytes - r.size(); + else + junkToSkip = 0; + } else { + quint64 origPos = source->pos(); + source->seek(source->pos() + numBytes); + junkToSkip = origPos + numBytes - source->pos(); + } +} + +QT_END_NAMESPACE + +#include "moc_qwavedecoder_p.cpp" diff --git a/src/multimedia/audio/qwavedecoder_p.h b/src/multimedia/audio/qwavedecoder_p.h new file mode 100644 index 000000000..1a7bef945 --- /dev/null +++ b/src/multimedia/audio/qwavedecoder_p.h @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** 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 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$ +** +****************************************************************************/ + +#ifndef WAVEDECODER_H +#define WAVEDECODER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qiodevice.h> +#include <qaudioformat.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +QT_MODULE(Multimedia) + + + +class QWaveDecoder : public QIODevice +{ + Q_OBJECT + +public: + explicit QWaveDecoder(QIODevice *source, QObject *parent = 0); + ~QWaveDecoder(); + + QAudioFormat audioFormat() const; + int duration() const; + + qint64 size() const; + bool isSequential() const; + qint64 bytesAvailable() const; + +Q_SIGNALS: + void formatKnown(); + void parsingError(); + +private Q_SLOTS: + void handleData(); + +private: + qint64 readData(char *data, qint64 maxlen); + qint64 writeData(const char *data, qint64 len); + + bool enoughDataAvailable(); + bool findChunk(const char *chunkId); + void discardBytes(qint64 numBytes); + void parsingFailed(); + + enum State { + InitialState, + WaitingForFormatState, + WaitingForDataState + }; + + struct chunk + { + char id[4]; + quint32 size; + }; + bool peekChunk(chunk* pChunk); + + struct RIFFHeader + { + chunk descriptor; + char type[4]; + }; + struct WAVEHeader + { + chunk descriptor; + quint16 audioFormat; + quint16 numChannels; + quint32 sampleRate; + quint32 byteRate; + quint16 blockAlign; + quint16 bitsPerSample; + }; + + bool haveFormat; + qint64 dataSize; + QAudioFormat format; + QIODevice *source; + State state; + quint32 junkToSkip; + bool bigEndian; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // WAVEDECODER_H |