diff options
author | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
---|---|---|
committer | Michael Goddard <michael.goddard@nokia.com> | 2011-06-29 13:38:46 +1000 |
commit | 2a34e88c1e1ced28e75c487cd13402e1c9cf9fa3 (patch) | |
tree | e6c1b770c5c47212792a1f9344fa034ea3e54c44 /src/multimediakit/effects |
Initial copy of QtMultimediaKit.
Comes from original repo, with SHA1:
2c82d5611655e5967f5c5095af50c0991c4378b2
Diffstat (limited to 'src/multimediakit/effects')
-rw-r--r-- | src/multimediakit/effects/effects.pri | 32 | ||||
-rw-r--r-- | src/multimediakit/effects/qsamplecache_p.cpp | 398 | ||||
-rw-r--r-- | src/multimediakit/effects/qsamplecache_p.h | 158 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect.cpp | 301 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_p.h | 140 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_pulse_p.cpp | 955 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_pulse_p.h | 160 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_qmedia_p.cpp | 233 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_qmedia_p.h | 119 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_qsound_p.cpp | 222 | ||||
-rw-r--r-- | src/multimediakit/effects/qsoundeffect_qsound_p.h | 118 | ||||
-rw-r--r-- | src/multimediakit/effects/qwavedecoder_p.cpp | 232 | ||||
-rw-r--r-- | src/multimediakit/effects/qwavedecoder_p.h | 134 |
13 files changed, 3202 insertions, 0 deletions
diff --git a/src/multimediakit/effects/effects.pri b/src/multimediakit/effects/effects.pri new file mode 100644 index 000000000..911bd9b62 --- /dev/null +++ b/src/multimediakit/effects/effects.pri @@ -0,0 +1,32 @@ +INCLUDEPATH += effects + +unix:!mac:!symbian { + contains(pulseaudio_enabled, yes) { + CONFIG += link_pkgconfig + PKGCONFIG += libpulse + + DEFINES += QT_MULTIMEDIA_PULSEAUDIO + PRIVATE_HEADERS += effects/qsoundeffect_pulse_p.h + SOURCES += effects/qsoundeffect_pulse_p.cpp + !maemo*:DEFINES += QTM_PULSEAUDIO_DEFAULTBUFFER + } else { + DEFINES += QT_MULTIMEDIA_QMEDIAPLAYER + PRIVATE_HEADERS += effects/qsoundeffect_qmedia_p.h + SOURCES += effects/qsoundeffect_qmedia_p.cpp + } +} else { + PRIVATE_HEADERS += effects/qsoundeffect_qsound_p.h + SOURCES += effects/qsoundeffect_qsound_p.cpp +} + +PRIVATE_HEADERS += \ + effects/qsoundeffect_p.h \ + effects/qwavedecoder_p.h \ + effects/qsamplecache_p.h + +SOURCES += \ + effects/qsoundeffect.cpp \ + effects/qwavedecoder_p.cpp \ + effects/qsamplecache_p.cpp + +HEADERS += diff --git a/src/multimediakit/effects/qsamplecache_p.cpp b/src/multimediakit/effects/qsamplecache_p.cpp new file mode 100644 index 000000000..d86aa2493 --- /dev/null +++ b/src/multimediakit/effects/qsamplecache_p.cpp @@ -0,0 +1,398 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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() +{ + delete m_waveDecoder; + delete m_stream; + 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() == "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() == "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() == "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(invalidFormat()), SLOT(decoderError())); + connect(m_waveDecoder, SIGNAL(readyRead()), SLOT(readSample())); +} + +// Called in loading thread +void QSample::decoderError() +{ + Q_ASSERT(QThread::currentThread()->objectName() == "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() == "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/multimediakit/effects/qsamplecache_p.h b/src/multimediakit/effects/qsamplecache_p.h new file mode 100644 index 000000000..327f3bd8e --- /dev/null +++ b/src/multimediakit/effects/qsamplecache_p.h @@ -0,0 +1,158 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 + +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/multimediakit/effects/qsoundeffect.cpp b/src/multimediakit/effects/qsoundeffect.cpp new file mode 100644 index 000000000..4d489871d --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect.cpp @@ -0,0 +1,301 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_p.h" + +#if defined(QT_MULTIMEDIA_PULSEAUDIO) +#include "qsoundeffect_pulse_p.h" +#elif(QT_MULTIMEDIA_QMEDIAPLAYER) +#include "qsoundeffect_qmedia_p.h" +#else +#include "qsoundeffect_qsound_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 QtMultimediaKit + + This element is part of the \bold{QtMultimediaKit 1.1} 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 and Symbian. +*/ + +/*! + \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::loopsChanged() + \since 1.0 + + This handler is called when the 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 +*/ + + +/*! + \internal + \since 1.0 +*/ + +QSoundEffect::QSoundEffect(QObject *parent) : + QObject(parent) +{ + d = new QSoundEffectPrivate(this); + 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->deleteLater(); +} + +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(); +} + +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_p.cpp" diff --git a/src/multimediakit/effects/qsoundeffect_p.h b/src/multimediakit/effects/qsoundeffect_p.h new file mode 100644 index 000000000..7f6b24a00 --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_p.h @@ -0,0 +1,140 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_P_H +#define QSOUNDEFFECT_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 <qmobilityglobal.h> +#include <QtCore/qobject.h> +#include <QtCore/qurl.h> +#include <QtCore/qstringlist.h> + + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +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(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; + 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 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/multimediakit/effects/qsoundeffect_pulse_p.cpp b/src/multimediakit/effects/qsoundeffect_pulse_p.cpp new file mode 100644 index 000000000..f6abf140e --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_pulse_p.cpp @@ -0,0 +1,955 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_5) || 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) + { + dest->channels = 2; + dest->values[0] = dest->values[1] = m_vol * soundEffectVolume / 100; + 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_5) || 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_5) || 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(); + } +}; +} + +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) +{ + pa_sample_spec_init(&m_pulseSpec); +} + +QSoundEffectPrivate::~QSoundEffectPrivate() +{ + unloadPulseStream(); + + if (m_sample) + m_sample->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; + m_runningCount = 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; + } +} + +int QSoundEffectPrivate::loopCount() const +{ + return m_loopCount; +} + +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; + pa_operation_unref(pa_context_set_sink_input_volume(daemon()->context(), m_sinkInputId, daemon()->calcVolume(&volume, m_volume), setvolume_callback, this)); + 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, this)); +#ifdef QT_PA_DEBUG + qDebug() << this << "updateMuted = " << daemon()->calcMuted(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::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_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 + m_runningCount = 0; + m_playQueued = true; + Q_ASSERT(m_pulseStream); + emptyStream(); + return; + } + m_runningCount = m_loopCount; + playSample(); + } + + setPlaying(true); +} + +void QSoundEffectPrivate::emptyStream() +{ + m_emptying = true; + pa_stream_set_write_callback(m_pulseStream, 0, this); + pa_stream_set_underflow_callback(m_pulseStream, 0, this); + pa_operation_unref(pa_stream_flush(m_pulseStream, stream_flush_callback, this)); +} + +void QSoundEffectPrivate::emptyComplete() +{ + PulseDaemonLocker locker; + m_emptying = false; + pa_operation_unref(pa_stream_cork(m_pulseStream, 1, stream_cork_callback, this)); +} + +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_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, this); + } 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_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_reset_buffer_callback, this); + } else if (bufferAttr->prebuf > uint32_t(m_sample->data().size())) { + pa_buffer_attr newBufferAttr; + newBufferAttr = *bufferAttr; + newBufferAttr.prebuf = m_sample->data().size(); + pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, this); + } 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; + m_runningCount = 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) + m_runningCount--; + 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) + m_runningCount--; + 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) + m_runningCount--; + 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(); + m_runningCount = 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, userdata); + } 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) +{ + Q_UNUSED(s); + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to reset buffer attribute"); + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#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) +{ + Q_UNUSED(s); + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to adjust pre-buffer attribute"); + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#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) +{ + Q_UNUSED(c); + Q_UNUSED(userdata); +#ifdef QT_PA_DEBUG + qDebug() << reinterpret_cast<QSoundEffectPrivate*>(userdata) << "setvolume_callback"; +#endif + if (!success) { + qWarning("QSoundEffect(pulseaudio): faild to set volume"); + } +} + +void QSoundEffectPrivate::setmuted_callback(pa_context *c, int success, void *userdata) +{ + Q_UNUSED(c); + Q_UNUSED(userdata); +#ifdef QT_PA_DEBUG + qDebug() << reinterpret_cast<QSoundEffectPrivate*>(userdata) << "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) +{ + Q_UNUSED(s); + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to stop"); + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#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) +{ + Q_UNUSED(s); + if (!success) + qWarning("QSoundEffect(pulseaudio): faild to drain"); + QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata); +#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/multimediakit/effects/qsoundeffect_pulse_p.h b/src/multimediakit/effects/qsoundeffect_pulse_p.h new file mode 100644 index 000000000..d07e9b184 --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_pulse_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_p.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 + +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; + 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; + +public Q_SLOTS: + void play(); + void stop(); + +Q_SIGNALS: + 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); + + 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; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSOUNDEFFECT_PULSE_H diff --git a/src/multimediakit/effects/qsoundeffect_qmedia_p.cpp b/src/multimediakit/effects/qsoundeffect_qmedia_p.cpp new file mode 100644 index 000000000..242997191 --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_qmedia_p.cpp @@ -0,0 +1,233 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_player(0), + m_status(QSoundEffect::Null), + m_playing(false) +{ + 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())); +} + +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; +} + +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) { + m_runningCount = -1; + } + else { + if (m_runningCount < 0) + m_runningCount = 0; + m_runningCount += m_loopCount; + } + m_player->play(); +} + +void QSoundEffectPrivate::stop() +{ + m_runningCount = 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 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(); +} + +QT_END_NAMESPACE + +#include "moc_qsoundeffect_qmedia_p.cpp" diff --git a/src/multimediakit/effects/qsoundeffect_qmedia_p.h b/src/multimediakit/effects/qsoundeffect_qmedia_p.h new file mode 100644 index 000000000..7351350e6 --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_qmedia_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + + +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; + 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; + +public Q_SLOTS: + void play(); + void stop(); + +Q_SIGNALS: + 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); + + 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/multimediakit/effects/qsoundeffect_qsound_p.cpp b/src/multimediakit/effects/qsoundeffect_qsound_p.cpp new file mode 100644 index 000000000..7a753d18c --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_qsound_p.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_qsound_p.h" + +#include <QtCore/qcoreapplication.h> +#include <QtGui/qsound.h> +#include <QtCore/qstringlist.h> + + +QT_BEGIN_NAMESPACE + +QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent): + QObject(parent), + m_playing(false), + m_timerID(0), + m_muted(false), + m_loopCount(1), + m_volume(100), + m_status(QSoundEffect::Null), + m_sound(0) +{ + if (!QSound::isAvailable()) + qWarning("SoundEffect(qsound) : not available"); +} + +QSoundEffectPrivate::~QSoundEffectPrivate() +{ +} + +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) +{ + if (url.isEmpty()) { + m_source = QUrl(); + setStatus(QSoundEffect::Null); + return; + } + + if (url.scheme() != QLatin1String("file")) { + m_source = url; + setStatus(QSoundEffect::Error); + return; + } + + if (m_sound != 0) + delete m_sound; + + m_source = url; + m_sound = new QSound(m_source.toLocalFile(), this); + m_sound->setLoops(m_loopCount); + m_status = QSoundEffect::Ready; + emit statusChanged(); + emit loadedChanged(); +} + +int QSoundEffectPrivate::loopCount() const +{ + return m_loopCount; +} + +void QSoundEffectPrivate::setLoopCount(int lc) +{ + m_loopCount = lc; + if (m_sound) + m_sound->setLoops(lc); +} + +int QSoundEffectPrivate::volume() const +{ + return m_volume; +} + +void QSoundEffectPrivate::setVolume(int v) +{ + m_volume = v; +} + +bool QSoundEffectPrivate::isMuted() const +{ + return m_muted; +} + +void QSoundEffectPrivate::setMuted(bool muted) +{ + m_muted = muted; +} + +bool QSoundEffectPrivate::isLoaded() const +{ + return m_status == QSoundEffect::Ready; +} + +void QSoundEffectPrivate::play() +{ + if (m_status == QSoundEffect::Null || m_status == QSoundEffect::Error) + return; + if (m_timerID != 0) + killTimer(m_timerID); + m_timerID = startTimer(500); + m_sound->play(); + setPlaying(true); +} + + +void QSoundEffectPrivate::stop() +{ + if (m_timerID != 0) + killTimer(m_timerID); + m_timerID = 0; + m_sound->stop(); + setPlaying(false); +} + +bool QSoundEffectPrivate::isPlaying() +{ + if (m_playing && m_sound && m_sound->isFinished()) { + if (m_timerID != 0) + killTimer(m_timerID); + m_timerID = 0; + setPlaying(false); + } + return m_playing; +} + +QSoundEffect::Status QSoundEffectPrivate::status() const +{ + return m_status; +} + +void QSoundEffectPrivate::timerEvent(QTimerEvent *event) +{ + Q_UNUSED(event); + setPlaying(!m_sound->isFinished()); + if (isPlaying()) + return; + killTimer(m_timerID); + m_timerID = 0; +} + +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(); +} + +QT_END_NAMESPACE + +#include "moc_qsoundeffect_qsound_p.cpp" diff --git a/src/multimediakit/effects/qsoundeffect_qsound_p.h b/src/multimediakit/effects/qsoundeffect_qsound_p.h new file mode 100644 index 000000000..2a7691b23 --- /dev/null +++ b/src/multimediakit/effects/qsoundeffect_qsound_p.h @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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_QSOUND_H +#define QSOUNDEFFECT_QSOUND_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 "qsoundeffect_p.h" + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE + +class QSound; + +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; + void setLoopCount(int loopCount); + int volume() const; + void setVolume(int volume); + bool isMuted() const; + void setMuted(bool muted); + bool isLoaded() const; + bool isPlaying(); + QSoundEffect::Status status() const; + +public Q_SLOTS: + void play(); + void stop(); + +Q_SIGNALS: + void volumeChanged(); + void mutedChanged(); + void loadedChanged(); + void playingChanged(); + void statusChanged(); + +private: + void setStatus(QSoundEffect::Status status); + void setPlaying(bool playing); + void timerEvent(QTimerEvent *event); + + bool m_playing; + int m_timerID; + bool m_muted; + int m_loopCount; + int m_volume; + QSoundEffect::Status m_status; + QSound *m_sound; + QUrl m_source; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // QSOUNDEFFECT_QSOUND_H diff --git a/src/multimediakit/effects/qwavedecoder_p.cpp b/src/multimediakit/effects/qwavedecoder_p.cpp new file mode 100644 index 000000000..992bb0996 --- /dev/null +++ b/src/multimediakit/effects/qwavedecoder_p.cpp @@ -0,0 +1,232 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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), + remaining(0), + source(s), + state(QWaveDecoder::InitialState) +{ + 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::handleData() +{ + if (state == QWaveDecoder::InitialState) { + if (source->bytesAvailable() < qint64(sizeof(RIFFHeader))) + return; + + RIFFHeader riff; + source->read(reinterpret_cast<char *>(&riff), sizeof(RIFFHeader)); + + if (qstrncmp(riff.descriptor.id, "RIFF", 4) != 0 || + qstrncmp(riff.type, "WAVE", 4) != 0) { + source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + emit invalidFormat(); + + return; + } else { + state = QWaveDecoder::WaitingForFormatState; + } + } + + if (state == QWaveDecoder::WaitingForFormatState) { + if (findChunk("fmt ")) { + chunk descriptor; + source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); + + 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)); + + if (wave.audioFormat != 0 && wave.audioFormat != 1) { + source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + emit invalidFormat(); + + return; + } else { + int bps = qFromLittleEndian<quint16>(wave.bitsPerSample); + + format.setCodec(QLatin1String("audio/pcm")); + 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)); + dataSize = descriptor.size; + + haveFormat = true; + connect(source, SIGNAL(readyRead()), SIGNAL(readyRead())); + emit formatKnown(); + + return; + } + } + + if (source->atEnd()) { + source->disconnect(SIGNAL(readyRead()), this, SLOT(handleData())); + emit invalidFormat(); + + return; + } + +} + +bool QWaveDecoder::enoughDataAvailable() +{ + if (source->bytesAvailable() < qint64(sizeof(chunk))) + return false; + + chunk descriptor; + source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); + + if (source->bytesAvailable() < qint64(sizeof(chunk) + descriptor.size)) + return false; + + return true; +} + +bool QWaveDecoder::findChunk(const char *chunkId) +{ + if (source->bytesAvailable() < qint64(sizeof(chunk))) + return false; + + chunk descriptor; + source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); + + if (qstrncmp(descriptor.id, chunkId, 4) == 0) + return true; + + while (source->bytesAvailable() >= qint64(sizeof(chunk) + descriptor.size)) { + discardBytes(sizeof(chunk) + descriptor.size); + + source->peek(reinterpret_cast<char *>(&descriptor), sizeof(chunk)); + + if (qstrncmp(descriptor.id, chunkId, 4) == 0) + return true; + } + + return false; +} + +void QWaveDecoder::discardBytes(qint64 numBytes) +{ + if (source->isSequential()) + source->read(numBytes); + else + source->seek(source->pos() + numBytes); +} + +QT_END_NAMESPACE + +#include "moc_qwavedecoder_p.cpp" diff --git a/src/multimediakit/effects/qwavedecoder_p.h b/src/multimediakit/effects/qwavedecoder_p.h new file mode 100644 index 000000000..90dfda811 --- /dev/null +++ b/src/multimediakit/effects/qwavedecoder_p.h @@ -0,0 +1,134 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 + + +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 invalidFormat(); + +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); + + enum State { + InitialState, + WaitingForFormatState, + WaitingForDataState + }; + + struct chunk + { + char id[4]; + quint32 size; + }; + 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; + qint64 remaining; + QAudioFormat format; + QIODevice *source; + State state; +}; + +QT_END_NAMESPACE + +QT_END_HEADER + +#endif // WAVEDECODER_H |