diff options
Diffstat (limited to 'src/multimedia/audio/qsoundeffect.cpp')
-rw-r--r-- | src/multimedia/audio/qsoundeffect.cpp | 206 |
1 files changed, 118 insertions, 88 deletions
diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp index f0bc57520..c12114672 100644 --- a/src/multimedia/audio/qsoundeffect.cpp +++ b/src/multimedia/audio/qsoundeffect.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include <QtMultimedia/private/qtmultimediaglobal_p.h> #include "qsoundeffect.h" @@ -43,14 +7,39 @@ #include "qaudiodevice.h" #include "qaudiosink.h" #include "qmediadevices.h" +#include "qaudiobuffer.h" #include <QtCore/qloggingcategory.h> +#include <private/qplatformmediadevices_p.h> +#include <private/qplatformmediaintegration_p.h> +#include <private/qplatformaudioresampler_p.h> -Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect") +static Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect") QT_BEGIN_NAMESPACE Q_GLOBAL_STATIC(QSampleCache, sampleCache) +namespace +{ +struct AudioSinkDeleter +{ + void operator ()(QAudioSink* sink) const + { + sink->stop(); + // Investigate:should we just delete? + sink->deleteLater(); + } +}; + +struct SampleDeleter +{ + void operator ()(QSample* sample) const + { + sample->release(); + } +}; +} + class QSoundEffectPrivate : public QIODevice { public: @@ -62,13 +51,14 @@ public: qint64 size() const override { if (m_sample->state() != QSample::Ready) return 0; - return m_loopCount == QSoundEffect::Infinite ? 0 : m_loopCount * m_sample->data().size(); + return m_loopCount == QSoundEffect::Infinite ? 0 : m_loopCount * m_audioBuffer.byteCount(); } qint64 bytesAvailable() const override { if (m_sample->state() != QSample::Ready) return 0; - return m_loopCount == QSoundEffect::Infinite - ? std::numeric_limits<qint64>::max() : m_runningCount * m_sample->data().size() - m_offset; + if (m_loopCount == QSoundEffect::Infinite) + return std::numeric_limits<qint64>::max(); + return m_runningCount * m_audioBuffer.byteCount() - m_offset; } bool isSequential() const override { return m_loopCount == QSoundEffect::Infinite; @@ -92,9 +82,10 @@ public: int m_loopCount = 1; int m_runningCount = 0; bool m_playing = false; - QSoundEffect::Status m_status = QSoundEffect::Null; - QAudioSink *m_audioOutput = nullptr; - QSample *m_sample = nullptr; + QSoundEffect::Status m_status = QSoundEffect::Null; + std::unique_ptr<QAudioSink, AudioSinkDeleter> m_audioSink; + std::unique_ptr<QSample, SampleDeleter> m_sample; + QAudioBuffer m_audioBuffer; bool m_muted = false; float m_volume = 1.0; bool m_sampleReady = false; @@ -108,6 +99,8 @@ QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &au , m_audioDevice(audioDevice) { open(QIODevice::ReadOnly); + + QPlatformMediaIntegration::instance()->mediaDevices()->prepareAudio(); } void QSoundEffectPrivate::sampleReady() @@ -116,30 +109,67 @@ void QSoundEffectPrivate::sampleReady() return; qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:" << m_sample->data().size(); - disconnect(m_sample, &QSample::error, this, &QSoundEffectPrivate::decoderError); - disconnect(m_sample, &QSample::ready, this, &QSoundEffectPrivate::sampleReady); - if (!m_audioOutput) { - m_audioOutput = new QAudioSink(m_audioDevice, m_sample->format()); - connect(m_audioOutput, &QAudioSink::stateChanged, this, &QSoundEffectPrivate::stateChanged); + disconnect(m_sample.get(), &QSample::error, this, &QSoundEffectPrivate::decoderError); + disconnect(m_sample.get(), &QSample::ready, this, &QSoundEffectPrivate::sampleReady); + if (!m_audioSink) { + const auto audioDevice = + m_audioDevice.isNull() ? QMediaDevices::defaultAudioOutput() : m_audioDevice; + + if (audioDevice.isNull()) { + // We are likely on a virtual machine, for example in CI + qCCritical(qLcSoundEffect) << "Failed to play sound. No audio devices present."; + setStatus(QSoundEffect::Error); + return; + } + + const auto &sampleFormat = m_sample->format(); + const auto sampleChannelConfig = + sampleFormat.channelConfig() == QAudioFormat::ChannelConfigUnknown + ? QAudioFormat::defaultChannelConfigForChannelCount(sampleFormat.channelCount()) + : sampleFormat.channelConfig(); + + if (sampleChannelConfig != audioDevice.channelConfiguration() + && audioDevice.channelConfiguration() != QAudioFormat::ChannelConfigUnknown) { + qCDebug(qLcSoundEffect) << "Create resampler for channels mapping: config" + << sampleFormat.channelConfig() << "=> config" + << audioDevice.channelConfiguration(); + auto outputFormat = sampleFormat; + outputFormat.setChannelConfig(audioDevice.channelConfiguration()); + + const auto resampler = QPlatformMediaIntegration::instance()->createAudioResampler( + m_sample->format(), outputFormat); + if (resampler) + m_audioBuffer = resampler.value()->resample(m_sample->data().constData(), + m_sample->data().size()); + else + qCDebug(qLcSoundEffect) << "Cannot create resampler for channels mapping"; + } + + if (!m_audioBuffer.isValid()) + m_audioBuffer = QAudioBuffer(m_sample->data(), m_sample->format()); + + m_audioSink.reset(new QAudioSink(audioDevice, m_audioBuffer.format())); + + connect(m_audioSink.get(), &QAudioSink::stateChanged, this, &QSoundEffectPrivate::stateChanged); if (!m_muted) - m_audioOutput->setVolume(m_volume); + m_audioSink->setVolume(m_volume); else - m_audioOutput->setVolume(0); + m_audioSink->setVolume(0); } m_sampleReady = true; setStatus(QSoundEffect::Ready); - if (m_playing && m_audioOutput->state() == QAudio::StoppedState) { + if (m_playing && m_audioSink->state() == QAudio::StoppedState) { qCDebug(qLcSoundEffect) << this << "starting playback on audiooutput"; - m_audioOutput->start(this); + m_audioSink->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); + disconnect(m_sample.get(), &QSample::ready, this, &QSoundEffectPrivate::sampleReady); + disconnect(m_sample.get(), &QSample::error, this, &QSoundEffectPrivate::decoderError); m_playing = false; setStatus(QSoundEffect::Error); } @@ -148,7 +178,7 @@ void QSoundEffectPrivate::stateChanged(QAudio::State state) { qCDebug(qLcSoundEffect) << this << "stateChanged " << state; if ((state == QAudio::IdleState && m_runningCount == 0) || state == QAudio::StoppedState) - emit q_ptr->stop(); + q_ptr->stop(); } qint64 QSoundEffectPrivate::readData(char *data, qint64 len) @@ -163,8 +193,8 @@ qint64 QSoundEffectPrivate::readData(char *data, qint64 len) qint64 bytesWritten = 0; - const int sampleSize = m_sample->data().size(); - const char* sampleData = m_sample->data().constData(); + const int sampleSize = m_audioBuffer.byteCount(); + const char *sampleData = m_audioBuffer.constData<char>(); while (len && m_runningCount) { int toWrite = qMin(sampleSize - m_offset, len); @@ -214,8 +244,8 @@ void QSoundEffectPrivate::setStatus(QSoundEffect::Status status) void QSoundEffectPrivate::setPlaying(bool playing) { qCDebug(qLcSoundEffect) << this << "setPlaying(" << playing << ")" << m_playing; - if (m_audioOutput) { - m_audioOutput->stop(); + if (m_audioSink) { + m_audioSink->stop(); if (playing && !m_sampleReady) return; } @@ -224,8 +254,8 @@ void QSoundEffectPrivate::setPlaying(bool playing) return; m_playing = playing; - if (m_audioOutput && playing) - m_audioOutput->start(this); + if (m_audioSink && playing) + m_audioSink->start(this); emit q_ptr->playingChanged(); } @@ -316,11 +346,8 @@ QSoundEffect::QSoundEffect(const QAudioDevice &audioDevice, QObject *parent) QSoundEffect::~QSoundEffect() { stop(); - if (d->m_audioOutput) { - d->m_audioOutput->stop(); - d->m_audioOutput->deleteLater(); - d->m_sample->release(); - } + d->m_audioSink.reset(); + d->m_sample.reset(); delete d; } @@ -391,24 +418,22 @@ void QSoundEffect::setSource(const QUrl &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); + disconnect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError); + disconnect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady); } d->m_sample->release(); d->m_sample = nullptr; } - if (d->m_audioOutput) { - QObject::disconnect(d->m_audioOutput, &QAudioSink::stateChanged, d, &QSoundEffectPrivate::stateChanged); - d->m_audioOutput->stop(); - d->m_audioOutput->deleteLater(); - d->m_audioOutput = nullptr; + if (d->m_audioSink) { + disconnect(d->m_audioSink.get(), &QAudioSink::stateChanged, d, &QSoundEffectPrivate::stateChanged); + d->m_audioSink.reset(); } 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); + d->m_sample.reset(sampleCache()->requestSample(url)); + connect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError); + connect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady); switch (d->m_sample->state()) { case QSample::Ready: @@ -486,6 +511,11 @@ void QSoundEffect::setLoopCount(int loopCount) emit loopCountChanged(); } +/*! + \property QSoundEffect::audioDevice + + Returns the QAudioDevice instance. +*/ QAudioDevice QSoundEffect::audioDevice() { return d->m_audioDevice; @@ -530,7 +560,7 @@ int QSoundEffect::loopsRemaining() const UI volume controls should usually be scaled non-linearly. For example, using a logarithmic scale will produce linear changes in perceived loudness, which is what a user would normally expect - from a volume control. See \l {QAudio::convertVolume()}{convertVolume()} + from a volume control. See \l {QtAudio::convertVolume()}{convertVolume()} for more details. */ /*! @@ -544,8 +574,8 @@ int QSoundEffect::loopsRemaining() const */ float QSoundEffect::volume() const { - if (d->m_audioOutput && !d->m_muted) - return d->m_audioOutput->volume(); + if (d->m_audioSink && !d->m_muted) + return d->m_audioSink->volume(); return d->m_volume; } @@ -560,7 +590,7 @@ float QSoundEffect::volume() const UI volume controls should usually be scaled non-linearly. For example, using a logarithmic scale will produce linear changes in perceived loudness, which is what a user would normally expect - from a volume control. See QAudio::convertVolume() for more details. + from a volume control. See QtAudio::convertVolume() for more details. */ void QSoundEffect::setVolume(float volume) { @@ -570,8 +600,8 @@ void QSoundEffect::setVolume(float volume) d->m_volume = volume; - if (d->m_audioOutput && !d->m_muted) - d->m_audioOutput->setVolume(volume); + if (d->m_audioSink && !d->m_muted) + d->m_audioSink->setVolume(volume); emit volumeChanged(); } @@ -606,10 +636,10 @@ void QSoundEffect::setMuted(bool muted) if (d->m_muted == muted) return; - 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); + if (muted && d->m_audioSink) + d->m_audioSink->setVolume(0); + else if (!muted && d->m_audioSink && d->m_muted) + d->m_audioSink->setVolume(d->m_volume); d->m_muted = muted; emit mutedChanged(); |