Files
qtmultimedia/src/multimedia/audio/qsoundeffect_qaudio_p.cpp
Yoann Lopes edc415be47 Fix crash in QSoundEffect when using it from a non-GUI thread.
The internal QIODevice used as data source was not set as child of the
parent QSoundEffectPrivate. If moveToThread() was called on the
QSoundEffect, the QIODevice would still receive events on the main
thread, leading to race conditions.

Task-number: QTBUG-46359
Change-Id: I180da2fb498108b316fd9b5b5cc84376b360fa3f
Reviewed-by: Christian Stromme <christian.stromme@theqtcompany.com>
2015-08-25 14:12:04 +00:00

442 lines
12 KiB
C++

/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** 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 http://www.qt.io/terms-conditions. For further
** information use the contact form at http://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 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $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()
{
}
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, SIGNAL(error()), d, SLOT(decoderError()));
disconnect(d->m_sample, SIGNAL(ready()), d, SLOT(sampleReady()));
}
d->m_sample->release();
d->m_sample = 0;
}
setStatus(QSoundEffect::Loading);
d->m_sample = sampleCache()->requestSample(url);
connect(d->m_sample, SIGNAL(error()), d, SLOT(decoderError()));
connect(d->m_sample, SIGNAL(ready()), d, SLOT(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):
QIODevice(s),
m_loopCount(1),
m_runningCount(0),
m_playing(false),
m_status(QSoundEffect::Null),
m_audioOutput(0),
m_sample(0),
m_muted(false),
m_volume(1.0),
m_sampleReady(false),
m_offset(0)
{
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, SIGNAL(error()), this, SLOT(decoderError()));
disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
if (!m_audioOutput) {
m_audioOutput = new QAudioOutput(m_sample->format());
connect(m_audioOutput,SIGNAL(stateChanged(QAudio::State)), this, SLOT(stateChanged(QAudio::State)));
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->start(this);
}
void PrivateSoundSource::decoderError()
{
qWarning("QSoundEffect(qaudio): Error decoding source");
disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
disconnect(m_sample, SIGNAL(error()), this, SLOT(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)
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);
#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"