diff options
author | Lars Knoll <lars.knoll@qt.io> | 2021-01-06 08:42:38 +0100 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2021-01-22 07:00:33 +0000 |
commit | c9964f051abadbc0f2228c60484a595873059e01 (patch) | |
tree | 51e9811f06d1785fae05e5cd523b2868b2c9e6f3 /src/multimedia/audio | |
parent | b898e5038b29a236578dc7cd1588bdbc0d40eac1 (diff) |
Clean up the QSoundEffect implementation
Change-Id: I2d1bb41575d0516492e7e9225fb022fbd62cdb0a
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/multimedia/audio')
-rw-r--r-- | src/multimedia/audio/audio.pri | 4 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect.cpp | 382 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_qaudio_p.cpp | 457 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect_qaudio_p.h | 151 |
4 files changed, 344 insertions, 650 deletions
diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index 352c157bd..aeb204481 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -18,7 +18,6 @@ PRIVATE_HEADERS += \ audio/qsamplecache_p.h \ audio/qaudiohelpers_p.h \ audio/qaudiosystem_p.h \ - audio/qsoundeffect_qaudio_p.h SOURCES += \ audio/qaudio.cpp \ @@ -34,8 +33,7 @@ SOURCES += \ audio/qaudiobuffer.cpp \ audio/qaudioprobe.cpp \ audio/qaudiodecoder.cpp \ - audio/qaudiohelpers.cpp \ - audio/qsoundeffect_qaudio_p.cpp + audio/qaudiohelpers.cpp android: include(opensles/opensles.pri) win32: include(windows/windows.pri) diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp index cf4a28028..97be1e0a8 100644 --- a/src/multimedia/audio/qsoundeffect.cpp +++ b/src/multimedia/audio/qsoundeffect.cpp @@ -39,10 +39,223 @@ #include <QtMultimedia/private/qtmultimediaglobal_p.h> #include "qsoundeffect.h" -#include "qsoundeffect_qaudio_p.h" +#include "qsamplecache_p.h" +#include "qaudiodeviceinfo.h" +#include "qaudiooutput.h" QT_BEGIN_NAMESPACE +Q_GLOBAL_STATIC(QSampleCache, sampleCache) + +class QSoundEffectPrivate : public QIODevice +{ +public: + QSoundEffectPrivate(QSoundEffect *q, const QAudioDeviceInfo &audioDevice = QAudioDeviceInfo()); + ~QSoundEffectPrivate() {} + + qint64 readData(char *data, qint64 len) override; + qint64 writeData(const char *data, qint64 len) override; + + void setLoopsRemaining(int loopsRemaining); + void setStatus(QSoundEffect::Status status); + void setPlaying(bool playing); + +public Q_SLOTS: + void sampleReady(); + void decoderError(); + void stateChanged(QAudio::State); + +public: + QSoundEffect *q_ptr; + QUrl m_url; + int m_loopCount = 1; + int m_runningCount = 0; + bool m_playing = false; + QSoundEffect::Status m_status = QSoundEffect::Null; + QAudioOutput *m_audioOutput = nullptr; + QSample *m_sample = nullptr; + bool m_muted = false; + qreal m_volume = 1.0; + bool m_sampleReady = false; + qint64 m_offset = 0; + QString m_category; + QAudioDeviceInfo m_audioDevice; +}; + +QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDeviceInfo &audioDevice) + : QIODevice(q) + , q_ptr(q) + , m_audioDevice(audioDevice) +{ + m_category = QLatin1String("game"); + open(QIODevice::ReadOnly); +} + +void QSoundEffectPrivate::sampleReady() +{ + if (m_status == QSoundEffect::Error) + return; + +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "sampleReady "<<m_playing; +#endif + disconnect(m_sample, &QSample::error, this, &QSoundEffectPrivate::decoderError); + disconnect(m_sample, &QSample::ready, this, &QSoundEffectPrivate::sampleReady); + if (!m_audioOutput) { + if (m_audioDevice.isNull()) + m_audioOutput = new QAudioOutput(m_sample->format()); + else + m_audioOutput = new QAudioOutput(m_audioDevice, m_sample->format()); + connect(m_audioOutput, &QAudioOutput::stateChanged, this, &QSoundEffectPrivate::stateChanged); + if (!m_muted) + m_audioOutput->setVolume(m_volume); + else + m_audioOutput->setVolume(0); + } + m_sampleReady = true; + setStatus(QSoundEffect::Ready); + + if (m_playing && m_audioOutput->state() == QAudio::StoppedState) + m_audioOutput->start(this); +} + +void QSoundEffectPrivate::decoderError() +{ + qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString())); + disconnect(m_sample, &QSample::ready, this, &QSoundEffectPrivate::sampleReady); + disconnect(m_sample, &QSample::error, this, &QSoundEffectPrivate::decoderError); + m_playing = false; + setStatus(QSoundEffect::Error); +} + +void QSoundEffectPrivate::stateChanged(QAudio::State state) +{ +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "stateChanged " << state; +#endif + if ((state == QAudio::IdleState && m_runningCount == 0) + || (state == QAudio::StoppedState && m_audioOutput->error() != QAudio::NoError)) + emit q_ptr->stop(); +} + +qint64 QSoundEffectPrivate::readData(char *data, qint64 len) +{ + if ((m_runningCount > 0 || m_runningCount == QSoundEffect::Infinite) && m_playing) { + + if (m_sample->state() != QSample::Ready) + return 0; + + qint64 bytesWritten = 0; + + const int periodSize = m_audioOutput->periodSize(); + const int sampleSize = m_sample->data().size(); + const char* sampleData = m_sample->data().constData(); + + // Some systems can have large buffers we only need a max of three + int periodsFree = qMin(3, (int)(m_audioOutput->bytesFree()/periodSize)); + int dataOffset = 0; + +#ifdef QT_QAUDIO_DEBUG + qDebug() << "bytesFree=" << m_audioOutput->bytesFree() << ", can fit " << periodsFree << " periodSize() chunks"; +#endif + + while ((periodsFree > 0) && (bytesWritten + periodSize + <= len)) { + + if (sampleSize - m_offset >= periodSize) { + // We can fit a whole period of data + memcpy(data + dataOffset, sampleData + m_offset, periodSize); + m_offset += periodSize; + dataOffset += periodSize; + bytesWritten += periodSize; +#ifdef QT_QAUDIO_DEBUG + qDebug() << "WHOLE PERIOD: bytesWritten=" << bytesWritten << ", offset=" << m_offset + << ", filesize=" << sampleSize; +#endif + } else { + // We are at end of sound, first write what is left of current sound + memcpy(data + dataOffset, sampleData + m_offset, sampleSize - m_offset); + bytesWritten += sampleSize - m_offset; + int wrapLen = periodSize - (sampleSize - m_offset); + if (wrapLen > sampleSize) + wrapLen = sampleSize; +#ifdef QT_QAUDIO_DEBUG + qDebug() << "END OF SOUND: bytesWritten=" << bytesWritten << ", offset=" << m_offset + << ", part1=" << (sampleSize-m_offset); +#endif + dataOffset += (sampleSize - m_offset); + m_offset = 0; + + if (m_runningCount > 0 && m_runningCount != QSoundEffect::Infinite) + setLoopsRemaining(m_runningCount-1); + + if (m_runningCount > 0 || m_runningCount == QSoundEffect::Infinite) { + // There are still more loops of this sound to play, append the start of sound to make up full period + memcpy(data + dataOffset, sampleData + m_offset, wrapLen); + m_offset += wrapLen; + dataOffset += wrapLen; + bytesWritten += wrapLen; +#ifdef QT_QAUDIO_DEBUG + qDebug() << "APPEND START FOR FULL PERIOD: bytesWritten=" << bytesWritten << ", offset=" << m_offset + << ", part2=" << wrapLen; + qDebug() << "part1 + part2 should be a period " << periodSize; +#endif + } + } + if (m_runningCount == 0) + break; + + periodsFree--; + } + return bytesWritten; + } + + return 0; +} + +qint64 QSoundEffectPrivate::writeData(const char *data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + return 0; +} + +void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining) +{ + if (m_runningCount == loopsRemaining) + return; +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "setLoopsRemaining " << loopsRemaining; +#endif + m_runningCount = loopsRemaining; + emit q_ptr->loopsRemainingChanged(); +} + +void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) +{ +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "setStatus" << status; +#endif + if (m_status == status) + return; + bool oldLoaded = q_ptr->isLoaded(); + m_status = status; + emit q_ptr->statusChanged(); + if (oldLoaded != q_ptr->isLoaded()) + emit q_ptr->loadedChanged(); +} + +void QSoundEffectPrivate::setPlaying(bool playing) +{ +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "setPlaying(" << playing << ")"; +#endif + if (m_playing == playing) + return; + m_playing = playing; + emit q_ptr->playingChanged(); +} + /*! \class QSoundEffect \brief The QSoundEffect class provides a way to play low latency sound effects. @@ -106,24 +319,11 @@ QT_BEGIN_NAMESPACE sound effects. */ -static QSoundEffectPrivate *initPrivate(QSoundEffect *self, QSoundEffectPrivate *d) -{ - QObject::connect(d, &QSoundEffectPrivate::loopsRemainingChanged, self, &QSoundEffect::loopsRemainingChanged); - QObject::connect(d, &QSoundEffectPrivate::volumeChanged, self, &QSoundEffect::volumeChanged); - QObject::connect(d, &QSoundEffectPrivate::mutedChanged, self, &QSoundEffect::mutedChanged); - QObject::connect(d, &QSoundEffectPrivate::loadedChanged, self, &QSoundEffect::loadedChanged); - QObject::connect(d, &QSoundEffectPrivate::playingChanged, self, &QSoundEffect::playingChanged); - QObject::connect(d, &QSoundEffectPrivate::statusChanged, self, &QSoundEffect::statusChanged); - QObject::connect(d, &QSoundEffectPrivate::categoryChanged, self, &QSoundEffect::categoryChanged); - - return d; -} /*! Creates a QSoundEffect with the given \a parent. */ QSoundEffect::QSoundEffect(QObject *parent) - : QObject(parent) - , d(initPrivate(this, new QSoundEffectPrivate(this))) + : QSoundEffect(QAudioDeviceInfo(), parent) { } @@ -132,7 +332,7 @@ QSoundEffect::QSoundEffect(QObject *parent) */ QSoundEffect::QSoundEffect(const QAudioDeviceInfo &audioDevice, QObject *parent) : QObject(parent) - , d(initPrivate(this, new QSoundEffectPrivate(audioDevice, this))) + , d(new QSoundEffectPrivate(this, audioDevice)) { } @@ -141,7 +341,13 @@ QSoundEffect::QSoundEffect(const QAudioDeviceInfo &audioDevice, QObject *parent) */ QSoundEffect::~QSoundEffect() { - d->release(); + stop(); + if (d->m_audioOutput) { + d->m_audioOutput->stop(); + d->m_audioOutput->deleteLater(); + d->m_sample->release(); + } + delete d; } /*! @@ -151,7 +357,15 @@ QSoundEffect::~QSoundEffect() */ QStringList QSoundEffect::supportedMimeTypes() { - return QSoundEffectPrivate::supportedMimeTypes(); + // Only return supported mime types if we have a audio device available + const QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + if (devices.size() <= 0) + return QStringList(); + + return QStringList() << QLatin1String("audio/x-wav") + << QLatin1String("audio/wav") + << QLatin1String("audio/wave") + << QLatin1String("audio/x-pn-wav"); } /*! @@ -173,16 +387,67 @@ QStringList QSoundEffect::supportedMimeTypes() /*! Returns the URL of the current source to play */ QUrl QSoundEffect::source() const { - return d->source(); + return d->m_url; } /*! Set the current URL to play to \a url. */ void QSoundEffect::setSource(const QUrl &url) { - if (d->source() == url) + if (d->m_url == url) + return; + +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "setSource current=" << d->m_url << ", to=" << url; +#endif + Q_ASSERT(d->m_url != url); + + stop(); + + d->m_url = url; + + d->m_sampleReady = false; + + if (url.isEmpty()) { + d->setStatus(QSoundEffect::Null); + return; + } + + if (!url.isValid()) { + d->setStatus(QSoundEffect::Error); return; + } - d->setSource(url); + if (d->m_sample) { + if (!d->m_sampleReady) { + QObject::disconnect(d->m_sample, &QSample::error, d, &QSoundEffectPrivate::decoderError); + QObject::disconnect(d->m_sample, &QSample::ready, d, &QSoundEffectPrivate::sampleReady); + } + d->m_sample->release(); + d->m_sample = nullptr; + } + + if (d->m_audioOutput) { + QObject::disconnect(d->m_audioOutput, &QAudioOutput::stateChanged, d, &QSoundEffectPrivate::stateChanged); + d->m_audioOutput->stop(); + d->m_audioOutput->deleteLater(); + d->m_audioOutput = nullptr; + } + + d->setStatus(QSoundEffect::Loading); + d->m_sample = sampleCache()->requestSample(url); + QObject::connect(d->m_sample, &QSample::error, d, &QSoundEffectPrivate::decoderError); + QObject::connect(d->m_sample, &QSample::ready, d, &QSoundEffectPrivate::sampleReady); + + switch (d->m_sample->state()) { + case QSample::Ready: + d->sampleReady(); + break; + case QSample::Error: + d->decoderError(); + break; + default: + break; + } emit sourceChanged(); } @@ -213,7 +478,7 @@ void QSoundEffect::setSource(const QUrl &url) */ int QSoundEffect::loopCount() const { - return d->loopCount(); + return d->m_loopCount; } /*! @@ -240,10 +505,12 @@ void QSoundEffect::setLoopCount(int loopCount) } if (loopCount == 0) loopCount = 1; - if (d->loopCount() == loopCount) + if (d->m_loopCount == loopCount) return; - d->setLoopCount(loopCount); + d->m_loopCount = loopCount; + if (d->m_playing) + d->setLoopsRemaining(loopCount); emit loopCountChanged(); } @@ -261,7 +528,7 @@ void QSoundEffect::setLoopCount(int loopCount) */ int QSoundEffect::loopsRemaining() const { - return d->loopsRemaining(); + return d->m_runningCount; } @@ -291,7 +558,10 @@ int QSoundEffect::loopsRemaining() const */ qreal QSoundEffect::volume() const { - return d->volume(); + if (d->m_audioOutput && !d->m_muted) + return d->m_audioOutput->volume(); + + return d->m_volume; } /*! @@ -309,10 +579,15 @@ qreal QSoundEffect::volume() const void QSoundEffect::setVolume(qreal volume) { volume = qBound(qreal(0.0), volume, qreal(1.0)); - if (qFuzzyCompare(d->volume(), volume)) + if (d->m_volume == volume) return; - d->setVolume(volume); + d->m_volume = volume; + + if (d->m_audioOutput && !d->m_muted) + d->m_audioOutput->setVolume(volume); + + emit volumeChanged(); } /*! @@ -330,7 +605,7 @@ void QSoundEffect::setVolume(qreal volume) /*! Returns whether this sound effect is muted */ bool QSoundEffect::isMuted() const { - return d->isMuted(); + return d->m_muted; } /*! @@ -342,10 +617,16 @@ bool QSoundEffect::isMuted() const */ void QSoundEffect::setMuted(bool muted) { - if (d->isMuted() == muted) + if (d->m_muted == muted) return; - d->setMuted(muted); + if (muted && d->m_audioOutput) + d->m_audioOutput->setVolume(0); + else if (!muted && d->m_audioOutput && d->m_muted) + d->m_audioOutput->setVolume(d->m_volume); + + d->m_muted = muted; + emit mutedChanged(); } /*! @@ -360,7 +641,7 @@ void QSoundEffect::setMuted(bool muted) */ bool QSoundEffect::isLoaded() const { - return d->isLoaded(); + return d->m_status == QSoundEffect::Ready; } /*! @@ -381,7 +662,18 @@ bool QSoundEffect::isLoaded() const */ void QSoundEffect::play() { - d->play(); + d->m_offset = 0; + d->setLoopsRemaining(d->m_loopCount); +#ifdef QT_QAUDIO_DEBUG + qDebug() << this << "play"; +#endif + if (d->m_status == QSoundEffect::Null || d->m_status == QSoundEffect::Error) { + d->setStatus(QSoundEffect::Null); + return; + } + d->setPlaying(true); + if (d->m_audioOutput && d->m_audioOutput->state() == QAudio::StoppedState && d->m_sampleReady) + d->m_audioOutput->start(d); } /*! @@ -398,7 +690,7 @@ void QSoundEffect::play() /*! Returns true if the sound effect is currently playing, or false otherwise */ bool QSoundEffect::isPlaying() const { - return d->isPlaying(); + return d->m_playing; } /*! @@ -438,7 +730,7 @@ bool QSoundEffect::isPlaying() const */ QSoundEffect::Status QSoundEffect::status() const { - return d->status(); + return d->m_status; } /*! @@ -479,7 +771,7 @@ QSoundEffect::Status QSoundEffect::status() const */ QString QSoundEffect::category() const { - return d->category(); + return d->m_category; } /*! @@ -500,7 +792,10 @@ QString QSoundEffect::category() const */ void QSoundEffect::setCategory(const QString &category) { - d->setCategory(category); + if (d->m_category != category && !d->m_playing) { + d->m_category = category; + emit categoryChanged(); + } } @@ -518,7 +813,17 @@ void QSoundEffect::setCategory(const QString &category) */ void QSoundEffect::stop() { - d->stop(); + if (!d->m_playing) + return; +#ifdef QT_QAUDIO_DEBUG + qDebug() << "stop()"; +#endif + d->m_offset = 0; + + d->setPlaying(false); + + if (d->m_audioOutput) + d->m_audioOutput->stop(); } /* Signals */ @@ -639,7 +944,6 @@ void QSoundEffect::stop() The corresponding handler is \c onCategoryChanged. */ - QT_END_NAMESPACE #include "moc_qsoundeffect.cpp" diff --git a/src/multimedia/audio/qsoundeffect_qaudio_p.cpp b/src/multimedia/audio/qsoundeffect_qaudio_p.cpp deleted file mode 100644 index 90d195d3f..000000000 --- a/src/multimedia/audio/qsoundeffect_qaudio_p.cpp +++ /dev/null @@ -1,457 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -// -// 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_qaudio_p.h" - -#include <QtCore/qcoreapplication.h> -#include <QtCore/qiodevice.h> - -//#include <QDebug> -//#define QT_QAUDIO_DEBUG 1 - -QT_BEGIN_NAMESPACE - -Q_GLOBAL_STATIC(QSampleCache, sampleCache) - -QSoundEffectPrivate::QSoundEffectPrivate(QObject *parent): - QObject(parent), - d(new PrivateSoundSource(this)) -{ -} - -QSoundEffectPrivate::QSoundEffectPrivate(const QAudioDeviceInfo &audioDevice, QObject *parent) - : QObject(parent) - , d(new PrivateSoundSource(this, audioDevice)) -{ -} - -QSoundEffectPrivate::~QSoundEffectPrivate() -{ -} - -void QSoundEffectPrivate::release() -{ - stop(); - if (d->m_audioOutput) { - d->m_audioOutput->stop(); - d->m_audioOutput->deleteLater(); - d->m_sample->release(); - } - delete d; - this->deleteLater(); -} - -QStringList QSoundEffectPrivate::supportedMimeTypes() -{ - // Only return supported mime types if we have a audio device available - const QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); - if (devices.size() <= 0) - return QStringList(); - - return QStringList() << QLatin1String("audio/x-wav") - << QLatin1String("audio/wav") - << QLatin1String("audio/wave") - << QLatin1String("audio/x-pn-wav"); -} - -QUrl QSoundEffectPrivate::source() const -{ - return d->m_url; -} - -void QSoundEffectPrivate::setSource(const QUrl &url) -{ -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "setSource current=" << d->m_url << ", to=" << url; -#endif - Q_ASSERT(d->m_url != url); - - stop(); - - d->m_url = url; - - d->m_sampleReady = false; - - if (url.isEmpty()) { - setStatus(QSoundEffect::Null); - return; - } - - if (!url.isValid()) { - setStatus(QSoundEffect::Error); - return; - } - - if (d->m_sample) { - if (!d->m_sampleReady) { - disconnect(d->m_sample, &QSample::error, d, &PrivateSoundSource::decoderError); - disconnect(d->m_sample, &QSample::ready, d, &PrivateSoundSource::sampleReady); - } - d->m_sample->release(); - d->m_sample = nullptr; - } - - if (d->m_audioOutput) { - disconnect(d->m_audioOutput, &QAudioOutput::stateChanged, d, &PrivateSoundSource::stateChanged); - d->m_audioOutput->stop(); - d->m_audioOutput->deleteLater(); - d->m_audioOutput = nullptr; - } - - setStatus(QSoundEffect::Loading); - d->m_sample = sampleCache()->requestSample(url); - connect(d->m_sample, &QSample::error, d, &PrivateSoundSource::decoderError); - connect(d->m_sample, &QSample::ready, d, &PrivateSoundSource::sampleReady); - - switch (d->m_sample->state()) { - case QSample::Ready: - d->sampleReady(); - break; - case QSample::Error: - d->decoderError(); - break; - default: - break; - } -} - -int QSoundEffectPrivate::loopCount() const -{ - return d->m_loopCount; -} - -int QSoundEffectPrivate::loopsRemaining() const -{ - return d->m_runningCount; -} - -void QSoundEffectPrivate::setLoopCount(int loopCount) -{ -#ifdef QT_QAUDIO_DEBUG - qDebug() << "setLoopCount " << loopCount; -#endif - if (loopCount == 0) - loopCount = 1; - d->m_loopCount = loopCount; - if (d->m_playing) - setLoopsRemaining(loopCount); -} - -qreal QSoundEffectPrivate::volume() const -{ - if (d->m_audioOutput && !d->m_muted) - return d->m_audioOutput->volume(); - - return d->m_volume; -} - -void QSoundEffectPrivate::setVolume(qreal volume) -{ - d->m_volume = volume; - - if (d->m_audioOutput && !d->m_muted) - d->m_audioOutput->setVolume(volume); - - emit volumeChanged(); -} - -bool QSoundEffectPrivate::isMuted() const -{ - return d->m_muted; -} - -void QSoundEffectPrivate::setMuted(bool muted) -{ - if (muted && d->m_audioOutput) - d->m_audioOutput->setVolume(0); - else if (!muted && d->m_audioOutput && d->m_muted) - d->m_audioOutput->setVolume(d->m_volume); - - d->m_muted = muted; - emit mutedChanged(); -} - -bool QSoundEffectPrivate::isLoaded() const -{ - return d->m_status == QSoundEffect::Ready; -} - - -bool QSoundEffectPrivate::isPlaying() const -{ - return d->m_playing; -} - -QSoundEffect::Status QSoundEffectPrivate::status() const -{ - return d->m_status; -} - -void QSoundEffectPrivate::play() -{ - d->m_offset = 0; - setLoopsRemaining(d->m_loopCount); -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "play"; -#endif - if (d->m_status == QSoundEffect::Null || d->m_status == QSoundEffect::Error) { - setStatus(QSoundEffect::Null); - return; - } - setPlaying(true); - if (d->m_audioOutput && d->m_audioOutput->state() == QAudio::StoppedState && d->m_sampleReady) - d->m_audioOutput->start(d); -} - -void QSoundEffectPrivate::stop() -{ - if (!d->m_playing) - return; -#ifdef QT_QAUDIO_DEBUG - qDebug() << "stop()"; -#endif - d->m_offset = 0; - - setPlaying(false); - - if (d->m_audioOutput) - d->m_audioOutput->stop(); -} - -void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) -{ -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "setStatus" << status; -#endif - if (d->m_status == status) - return; - bool oldLoaded = isLoaded(); - d->m_status = status; - emit statusChanged(); - if (oldLoaded != isLoaded()) - emit loadedChanged(); -} - -void QSoundEffectPrivate::setPlaying(bool playing) -{ -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "setPlaying(" << playing << ")"; -#endif - if (d->m_playing == playing) - return; - d->m_playing = playing; - emit playingChanged(); -} - -void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining) -{ - if (d->m_runningCount == loopsRemaining) - return; -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "setLoopsRemaining " << loopsRemaining; -#endif - d->m_runningCount = loopsRemaining; - emit loopsRemainingChanged(); -} - -/* Categories are ignored */ -QString QSoundEffectPrivate::category() const -{ - return d->m_category; -} - -void QSoundEffectPrivate::setCategory(const QString &category) -{ - if (d->m_category != category && !d->m_playing) { - d->m_category = category; - emit categoryChanged(); - } -} - -PrivateSoundSource::PrivateSoundSource(QSoundEffectPrivate *s, const QAudioDeviceInfo &audioDevice) - : QIODevice(s) - , m_audioDevice(audioDevice) -{ - soundeffect = s; - m_category = QLatin1String("game"); - open(QIODevice::ReadOnly); -} - -void PrivateSoundSource::sampleReady() -{ - if (m_status == QSoundEffect::Error) - return; - -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "sampleReady "<<m_playing; -#endif - disconnect(m_sample, &QSample::error, this, &PrivateSoundSource::decoderError); - disconnect(m_sample, &QSample::ready, this, &PrivateSoundSource::sampleReady); - if (!m_audioOutput) { - if (m_audioDevice.isNull()) - m_audioOutput = new QAudioOutput(m_sample->format()); - else - m_audioOutput = new QAudioOutput(m_audioDevice, m_sample->format()); - connect(m_audioOutput, &QAudioOutput::stateChanged, this, &PrivateSoundSource::stateChanged); - if (!m_muted) - m_audioOutput->setVolume(m_volume); - else - m_audioOutput->setVolume(0); - } - m_sampleReady = true; - soundeffect->setStatus(QSoundEffect::Ready); - - if (m_playing && m_audioOutput->state() == QAudio::StoppedState) - m_audioOutput->start(this); -} - -void PrivateSoundSource::decoderError() -{ - qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString())); - disconnect(m_sample, &QSample::ready, this, &PrivateSoundSource::sampleReady); - disconnect(m_sample, &QSample::error, this, &PrivateSoundSource::decoderError); - m_playing = false; - soundeffect->setStatus(QSoundEffect::Error); -} - -void PrivateSoundSource::stateChanged(QAudio::State state) -{ -#ifdef QT_QAUDIO_DEBUG - qDebug() << this << "stateChanged " << state; -#endif - if ((state == QAudio::IdleState && m_runningCount == 0) - || (state == QAudio::StoppedState && m_audioOutput->error() != QAudio::NoError)) - emit soundeffect->stop(); -} - -qint64 PrivateSoundSource::readData(char *data, qint64 len) -{ - if ((m_runningCount > 0 || m_runningCount == QSoundEffect::Infinite) && m_playing) { - - if (m_sample->state() != QSample::Ready) - return 0; - - qint64 bytesWritten = 0; - - const int periodSize = m_audioOutput->periodSize(); - const int sampleSize = m_sample->data().size(); - const char* sampleData = m_sample->data().constData(); - - // Some systems can have large buffers we only need a max of three - int periodsFree = qMin(3, (int)(m_audioOutput->bytesFree()/periodSize)); - int dataOffset = 0; - -#ifdef QT_QAUDIO_DEBUG - qDebug() << "bytesFree=" << m_audioOutput->bytesFree() << ", can fit " << periodsFree << " periodSize() chunks"; -#endif - - while ((periodsFree > 0) && (bytesWritten + periodSize <= len)) { - - if (sampleSize - m_offset >= periodSize) { - // We can fit a whole period of data - memcpy(data + dataOffset, sampleData + m_offset, periodSize); - m_offset += periodSize; - dataOffset += periodSize; - bytesWritten += periodSize; -#ifdef QT_QAUDIO_DEBUG - qDebug() << "WHOLE PERIOD: bytesWritten=" << bytesWritten << ", offset=" << m_offset - << ", filesize=" << sampleSize; -#endif - } else { - // We are at end of sound, first write what is left of current sound - memcpy(data + dataOffset, sampleData + m_offset, sampleSize - m_offset); - bytesWritten += sampleSize - m_offset; - int wrapLen = periodSize - (sampleSize - m_offset); - if (wrapLen > sampleSize) - wrapLen = sampleSize; -#ifdef QT_QAUDIO_DEBUG - qDebug() << "END OF SOUND: bytesWritten=" << bytesWritten << ", offset=" << m_offset - << ", part1=" << (sampleSize-m_offset); -#endif - dataOffset += (sampleSize - m_offset); - m_offset = 0; - - if (m_runningCount > 0 && m_runningCount != QSoundEffect::Infinite) - soundeffect->setLoopsRemaining(m_runningCount-1); - - if (m_runningCount > 0 || m_runningCount == QSoundEffect::Infinite) { - // There are still more loops of this sound to play, append the start of sound to make up full period - memcpy(data + dataOffset, sampleData + m_offset, wrapLen); - m_offset += wrapLen; - dataOffset += wrapLen; - bytesWritten += wrapLen; -#ifdef QT_QAUDIO_DEBUG - qDebug() << "APPEND START FOR FULL PERIOD: bytesWritten=" << bytesWritten << ", offset=" << m_offset - << ", part2=" << wrapLen; - qDebug() << "part1 + part2 should be a period " << periodSize; -#endif - } - } - if (m_runningCount == 0) - break; - - periodsFree--; - } - return bytesWritten; - } - - return 0; -} - -qint64 PrivateSoundSource::writeData(const char *data, qint64 len) -{ - Q_UNUSED(data); - Q_UNUSED(len); - return 0; -} - -QT_END_NAMESPACE - -#include "moc_qsoundeffect_qaudio_p.cpp" diff --git a/src/multimedia/audio/qsoundeffect_qaudio_p.h b/src/multimedia/audio/qsoundeffect_qaudio_p.h deleted file mode 100644 index a3a48f60d..000000000 --- a/src/multimedia/audio/qsoundeffect_qaudio_p.h +++ /dev/null @@ -1,151 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QSOUNDEFFECT_QAUDIO_H -#define QSOUNDEFFECT_QAUDIO_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 "qaudiooutput.h" -#include "qsamplecache_p.h" -#include "qsoundeffect.h" - -QT_BEGIN_NAMESPACE - -class QSoundEffectPrivate; - -class PrivateSoundSource : public QIODevice -{ - friend class QSoundEffectPrivate; - Q_OBJECT -public: - PrivateSoundSource(QSoundEffectPrivate *s, const QAudioDeviceInfo &audioDevice = QAudioDeviceInfo()); - ~PrivateSoundSource() {} - - qint64 readData(char *data, qint64 len) override; - qint64 writeData(const char *data, qint64 len) override; - -private Q_SLOTS: - void sampleReady(); - void decoderError(); - void stateChanged(QAudio::State); - -private: - QUrl m_url; - int m_loopCount = 1; - int m_runningCount = 0; - bool m_playing = false; - QSoundEffect::Status m_status = QSoundEffect::Null; - QAudioOutput *m_audioOutput = nullptr; - QSample *m_sample = nullptr; - bool m_muted = false; - qreal m_volume = 1.0; - bool m_sampleReady = false; - qint64 m_offset = 0; - QString m_category; - QAudioDeviceInfo m_audioDevice; - QSoundEffectPrivate *soundeffect = nullptr; -}; - - -class QSoundEffectPrivate : public QObject -{ - friend class PrivateSoundSource; - Q_OBJECT -public: - - explicit QSoundEffectPrivate(QObject *parent); - explicit QSoundEffectPrivate(const QAudioDeviceInfo &audioDevice, QObject *parent); - ~QSoundEffectPrivate(); - - 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; - QSoundEffect::Status status() const; - - void release(); - - QString category() const; - void setCategory(const QString &); - -public Q_SLOTS: - void play(); - void stop(); - -Q_SIGNALS: - void loopsRemainingChanged(); - void volumeChanged(); - void mutedChanged(); - void loadedChanged(); - void playingChanged(); - void statusChanged(); - void categoryChanged(); - -private: - void setStatus(QSoundEffect::Status status); - void setPlaying(bool playing); - void setLoopsRemaining(int loopsRemaining); - - PrivateSoundSource *d = nullptr; -}; - -QT_END_NAMESPACE - -#endif // QSOUNDEFFECT_QAUDIO_H |