summaryrefslogtreecommitdiffstats
path: root/src/multimedia/spatial/qspatialaudioengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/spatial/qspatialaudioengine.cpp')
-rw-r--r--src/multimedia/spatial/qspatialaudioengine.cpp627
1 files changed, 0 insertions, 627 deletions
diff --git a/src/multimedia/spatial/qspatialaudioengine.cpp b/src/multimedia/spatial/qspatialaudioengine.cpp
deleted file mode 100644
index 7ab89c1fa..000000000
--- a/src/multimedia/spatial/qspatialaudioengine.cpp
+++ /dev/null
@@ -1,627 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2022 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Spatial Audio module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL-NOGPL2$
-** 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 3 or (at your option) 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.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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-#include <qspatialaudioengine_p.h>
-#include <qspatialaudiosoundsource_p.h>
-#include <qspatialaudiostereosource.h>
-#include <qspatialaudioroom_p.h>
-#include <qspatialaudiolistener.h>
-#include <resonance_audio_api_extensions.h>
-#include <qambisonicdecoder_p.h>
-#include <qaudiodecoder.h>
-#include <qmediadevices.h>
-#include <qiodevice.h>
-#include <qaudiosink.h>
-#include <qdebug.h>
-#include <qelapsedtimer.h>
-
-QT_BEGIN_NAMESPACE
-
-class QAudioOutputStream : public QIODevice
-{
- Q_OBJECT
-public:
- explicit QAudioOutputStream(QSpatialAudioEnginePrivate *d)
- : d(d)
- {
- open(QIODevice::ReadOnly);
- }
- ~QAudioOutputStream();
-
- qint64 readData(char *data, qint64 len) override;
-
- qint64 writeData(const char *, qint64) override;
-
- qint64 size() const override { return 0; }
- qint64 bytesAvailable() const override {
- return std::numeric_limits<qint64>::max();
- }
- bool isSequential() const override {
- return true;
- }
- bool atEnd() const override {
- return false;
- }
- qint64 pos() const override {
- return m_pos;
- }
-
- Q_INVOKABLE void startOutput() {
- QMutexLocker l(&d->mutex);
- Q_ASSERT(!sink);
- d->ambisonicDecoder.reset(new QAmbisonicDecoder(QAmbisonicDecoder::HighQuality, d->format));
- sink.reset(new QAudioSink(d->device, d->format));
- sink->setBufferSize(16384);
- sink->start(this);
- }
-
- Q_INVOKABLE void stopOutput() {
- sink->stop();
- sink.reset();
- d->ambisonicDecoder.reset();
- delete this;
- }
-
- void setPaused(bool paused) {
- if (paused)
- sink->suspend();
- else
- sink->resume();
- }
-
-private:
- qint64 m_pos = 0;
- QSpatialAudioEnginePrivate *d = nullptr;
- std::unique_ptr<QAudioSink> sink;
-};
-
-
-QAudioOutputStream::~QAudioOutputStream()
-{
-}
-
-qint64 QAudioOutputStream::writeData(const char *, qint64)
-{
- return 0;
-}
-
-qint64 QAudioOutputStream::readData(char *data, qint64 len)
-{
- if (d->paused.loadRelaxed())
- return 0;
-
- d->updateRooms();
-
- int nChannels = d->ambisonicDecoder ? d->ambisonicDecoder->nOutputChannels() : 2;
- if (len < nChannels*int(sizeof(float))*QSpatialAudioEnginePrivate::bufferSize)
- return 0;
-
- short *fd = (short *)data;
- qint64 frames = len / nChannels / sizeof(short);
- bool ok = true;
- while (frames >= qint64(QSpatialAudioEnginePrivate::bufferSize)) {
- // Fill input buffers
- for (auto *source : qAsConst(d->sources)) {
- auto *sp = QSpatialAudioSoundSourcePrivate::get(source);
- float buf[QSpatialAudioEnginePrivate::bufferSize];
- sp->getBuffer(buf, QSpatialAudioEnginePrivate::bufferSize, 1);
- d->api->SetInterleavedBuffer(sp->sourceId, buf, 1, QSpatialAudioEnginePrivate::bufferSize);
- }
- for (auto *source : qAsConst(d->stereoSources)) {
- auto *sp = QSpatialAudioSound::get(source);
- float buf[2*QSpatialAudioEnginePrivate::bufferSize];
- sp->getBuffer(buf, QSpatialAudioEnginePrivate::bufferSize, 2);
- d->api->SetInterleavedBuffer(sp->sourceId, buf, 2, QSpatialAudioEnginePrivate::bufferSize);
- }
-
- if (d->ambisonicDecoder && d->outputMode == QSpatialAudioEngine::Normal && d->format.channelCount() != 2) {
- const float *channels[QAmbisonicDecoder::maxAmbisonicChannels];
- int nSamples = vraudio::getAmbisonicOutput(d->api, channels, d->ambisonicDecoder->nInputChannels());
- Q_ASSERT(d->ambisonicDecoder->nOutputChannels() <= 8);
- d->ambisonicDecoder->processBuffer(channels, fd, nSamples);
- } else {
- ok = d->api->FillInterleavedOutputBuffer(2, QSpatialAudioEnginePrivate::bufferSize, fd);
- if (!ok) {
- qWarning() << " Reading failed!";
- break;
- }
- }
- fd += nChannels*QSpatialAudioEnginePrivate::bufferSize;
- frames -= QSpatialAudioEnginePrivate::bufferSize;
- }
- const int bytesProcessed = ((char *)fd - data);
- m_pos += bytesProcessed;
- return bytesProcessed;
-}
-
-
-QSpatialAudioEnginePrivate::QSpatialAudioEnginePrivate()
-{
- device = QMediaDevices::defaultAudioOutput();
-}
-
-QSpatialAudioEnginePrivate::~QSpatialAudioEnginePrivate()
-{
- delete api;
-}
-
-void QSpatialAudioEnginePrivate::addSpatialSound(QSpatialAudioSoundSource *sound)
-{
- QSpatialAudioSound *sd = QSpatialAudioSound::get(sound);
-
- sd->sourceId = api->CreateSoundObjectSource(vraudio::kBinauralHighQuality);
- sources.append(sound);
-}
-
-void QSpatialAudioEnginePrivate::removeSpatialSound(QSpatialAudioSoundSource *sound)
-{
- QSpatialAudioSound *sd = QSpatialAudioSound::get(sound);
-
- api->DestroySource(sd->sourceId);
- sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
- sources.removeOne(sound);
-}
-
-void QSpatialAudioEnginePrivate::addStereoSound(QSpatialAudioStereoSource *sound)
-{
- QSpatialAudioSound *sd = QSpatialAudioSound::get(sound);
-
- sd->sourceId = api->CreateStereoSource(2);
- stereoSources.append(sound);
-}
-
-void QSpatialAudioEnginePrivate::removeStereoSound(QSpatialAudioStereoSource *sound)
-{
- QSpatialAudioSound *sd = QSpatialAudioSound::get(sound);
-
- api->DestroySource(sd->sourceId);
- sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
- stereoSources.removeOne(sound);
-}
-
-void QSpatialAudioEnginePrivate::addRoom(QSpatialAudioRoom *room)
-{
- rooms.append(room);
-}
-
-void QSpatialAudioEnginePrivate::removeRoom(QSpatialAudioRoom *room)
-{
- rooms.removeOne(room);
-}
-
-void QSpatialAudioEnginePrivate::updateRooms()
-{
- if (!roomEffectsEnabled)
- return;
-
- bool needUpdate = listenerPositionDirty;
- listenerPositionDirty = false;
-
- bool roomDirty = false;
- for (const auto &room : rooms) {
- auto *rd = QSpatialAudioRoomPrivate::get(room);
- if (rd->dirty) {
- roomDirty = true;
- rd->update();
- needUpdate = true;
- }
- }
-
- if (!needUpdate)
- return;
-
- QVector3D listenerPos = listenerPosition();
- float roomVolume = float(qInf());
- QSpatialAudioRoom *room = nullptr;
- // Find the smallest room that contains the listener and apply it's room effects
- for (auto *r : qAsConst(rooms)) {
- QVector3D dim2 = r->dimensions()/2.;
- float vol = dim2.x()*dim2.y()*dim2.z();
- if (vol > roomVolume)
- continue;
- QVector3D dist = r->position() - listenerPos;
- // transform into room coordinates
- dist = r->rotation().rotatedVector(dist);
- if (qAbs(dist.x()) <= dim2.x() &&
- qAbs(dist.y()) <= dim2.y() &&
- qAbs(dist.z()) <= dim2.z()) {
- room = r;
- roomVolume = vol;
- }
- }
- if (room != currentRoom)
- roomDirty = true;
- currentRoom = room;
-
- if (!roomDirty)
- return;
-
- // apply room to engine
- if (!currentRoom) {
- api->EnableRoomEffects(false);
- return;
- }
- QSpatialAudioRoomPrivate *rp = QSpatialAudioRoomPrivate::get(room);
- api->SetReflectionProperties(rp->reflections);
- api->SetReverbProperties(rp->reverb);
-
- // update room effects for all sound sources
- for (auto *s : qAsConst(sources)) {
- auto *sp = QSpatialAudioSoundSourcePrivate::get(s);
- sp->updateRoomEffects();
- }
-}
-
-QVector3D QSpatialAudioEnginePrivate::listenerPosition() const
-{
- return listener ? listener->position() : QVector3D();
-}
-
-
-/*!
- \class QSpatialAudioEngine
- \inmodule QtMultimedia
- \ingroup multimedia_spatialaudio
-
- \brief QSpatialAudioEngine manages a three dimensional sound field.
-
- You can use an instance of QSpatialAudioEngine to manage a sound field in
- three dimensions. A sound field is defined by several QSpatialAudioSoundSource
- objects that define a sound at a specified location in 3D space. You can also
- add stereo overlays using QSpatialAudioStereoSource.
-
- You can use QSpatialAudioListener to define the position of the person listening
- to the sound field relative to the sound sources. Sound sources will be less audible
- if the listener is further away from source. They will also get mapped to the corresponding
- loudspeakers depending on the direction between listener and source.
-
- QSpatialAudioEngine offers two output modes. The first mode renders the sound field to a set of
- speakers, either a stereo speaker pair or a surround configuration. The second mode provides
- an immersive 3D sound experience when using headphones.
-
- Perception of sound localization is driven mainly by two factors. The first factor is timing
- differences of the sound waves between left and right ear. The second factor comes from various
- ways how sounds coming from different direcations create different types of reflections from our
- ears and heads. See https://en.wikipedia.org/wiki/Sound_localization for more details.
-
- The spatial audio engine emulates those timing differences and reflections through
- Head related transfer functions (HRTF, see
- https://en.wikipedia.org/wiki/Head-related_transfer_function). The functions used emulates those
- effects for an average persons ears and head. It provides a good and immersive 3D sound localization
- experience for most persons when using headphones.
-
- The engine is rather versatile allowing you to define amd emulate room properties and reverb settings emulating
- different types of rooms.
-
- Sound sources can also be occluded dampening the sound coming from those sources.
-
-*/
-
-/*!
- Constructs a spatial audio engine with \a parent.
-
- The engine will operate with a sample rate given by \a sampleRate. Sound content that is
- not provided at that sample rate will automatically get resampled to \a sampleRate when
- being processed by the engine. The default sample rate is fine in most cases, but you can define
- a different rate if most of your sound files are sampled with a different rate, and avoid some
- CPU overhead for resampling.
- */
-QSpatialAudioEngine::QSpatialAudioEngine(QObject *parent, int sampleRate)
- : QObject(parent)
- , d(new QSpatialAudioEnginePrivate)
-{
- d->sampleRate = sampleRate;
- d->api = vraudio::CreateResonanceAudioApi(2, QSpatialAudioEnginePrivate::bufferSize, d->sampleRate);
-}
-
-/*!
- Destroys the spatial audio engine.
- */
-QSpatialAudioEngine::~QSpatialAudioEngine()
-{
- stop();
- delete d;
-}
-
-/*! \enum QSpatialAudioEngine::OutputMode
- \value Normal Map the sounds to the loudspeaker configuration of the output device.
- This is normally a stereo or surround speaker setup.
- \value Headphone Use Headphone spatialization to create a 3D audio effect when listening
- to the sound field through headphones
-*/
-
-/*!
- \property QSpatialAudioEngine::outputMode
-
- Sets or retrieves the current output mode of the engine.
-
- \sa QSpatialAudioEngine::OutputMode
- */
-void QSpatialAudioEngine::setOutputMode(OutputMode mode)
-{
- if (d->outputMode == mode)
- return;
- d->outputMode = mode;
- if (d->api) {
- d->api->SetStereoSpeakerMode(mode == Normal);
- }
- emit outputModeChanged();
-}
-
-QSpatialAudioEngine::OutputMode QSpatialAudioEngine::outputMode() const
-{
- return d->outputMode;
-}
-
-/*!
- Returns the sample rate the engine has been configured with.
- */
-int QSpatialAudioEngine::sampleRate() const
-{
- return d->sampleRate;
-}
-
-/*!
- \property QSpatialAudioEngine::outputDevice
-
- Sets or returns the device that is being used for playing the sound field.
- */
-void QSpatialAudioEngine::setOutputDevice(const QAudioDevice &device)
-{
- if (d->device == device)
- return;
- if (d->api) {
- qWarning() << "Changing device on a running engine not implemented";
- return;
- }
- d->device = device;
- emit outputDeviceChanged();
-}
-
-QAudioDevice QSpatialAudioEngine::outputDevice() const
-{
- return d->device;
-}
-
-/*!
- \property QSpatialAudioEngine::masterVolume
-
- Sets or returns volume being used to render the sound field.
- */
-void QSpatialAudioEngine::setMasterVolume(float volume)
-{
- if (d->masterVolume == volume)
- return;
- d->masterVolume = volume;
- d->api->SetMasterVolume(volume);
- emit masterVolumeChanged();
-}
-
-float QSpatialAudioEngine::masterVolume() const
-{
- return d->masterVolume;
-}
-
-/*!
- Starts the engine.
- */
-void QSpatialAudioEngine::start()
-{
- if (d->outputStream)
- // already started
- return;
-
- d->format.setChannelCount(2);
- d->format.setSampleRate(d->sampleRate);
- d->format.setSampleFormat(QAudioFormat::Int16);
-
- d->api->SetStereoSpeakerMode(d->outputMode == Normal);
- d->api->SetMasterVolume(d->masterVolume);
-
- d->outputStream.reset(new QAudioOutputStream(d));
- d->outputStream->moveToThread(&d->audioThread);
- d->audioThread.start();
-
- QMetaObject::invokeMethod(d->outputStream.get(), "startOutput");
-}
-
-/*!
- Stops the engine.
- */
-void QSpatialAudioEngine::stop()
-{
- QMetaObject::invokeMethod(d->outputStream.get(), "stopOutput", Qt::BlockingQueuedConnection);
- d->outputStream.reset();
- d->audioThread.exit(0);
- d->audioThread.wait();
- delete d->api;
- d->api = nullptr;
-}
-
-/*!
- \property QSpatialAudioEngine::paused
-
- Pauses the spatial audio engine.
- */
-void QSpatialAudioEngine::setPaused(bool paused)
-{
- bool old = d->paused.fetchAndStoreRelaxed(paused);
- if (old != paused) {
- if (d->outputStream)
- d->outputStream->setPaused(paused);
- emit pausedChanged();
- }
-}
-
-bool QSpatialAudioEngine::paused() const
-{
- return d->paused.loadRelaxed();
-}
-
-/*!
- Enables room effects such as echos and reverb.
-
- Enables room effects if \a enabled is true.
- Room effects will only apply if you create one or more \l QSpatialAudioRoom objects
- and the listener is inside at least one of the rooms. If the listener is inside
- multiple rooms, the room with the smallest volume will be used.
- */
-void QSpatialAudioEngine::setRoomEffectsEnabled(bool enabled)
-{
- if (d->roomEffectsEnabled == enabled)
- return;
- d->roomEffectsEnabled = enabled;
-}
-
-/*!
- Returns true if room effects are enabled.
- */
-bool QSpatialAudioEngine::roomEffectsEnabled() const
-{
- return d->roomEffectsEnabled;
-}
-
-/*!
- \property QSpatialAudioEngine::distanceScale
-
- Defines the scale of the coordinate system being used by the spatial audio engine.
- By default, all units are in centimeters, in line with the default units being
- used by Qt Quick 3D.
-
- Set the distance scale to QSpatialAudioEngine::DistanceScaleMeter to get units in meters.
-*/
-void QSpatialAudioEngine::setDistanceScale(float scale)
-{
- // multiply with 100, to get the conversion to meters that resonance audio uses
- scale /= 100.f;
- if (scale <= 0.0f) {
- qWarning() << "QSpatialAudioEngine: Invalid distance scale.";
- return;
- }
- if (scale == d->distanceScale)
- return;
- d->distanceScale = scale;
- emit distanceScaleChanged();
-}
-
-float QSpatialAudioEngine::distanceScale() const
-{
- return d->distanceScale*100.f;
-}
-
-
-/*! \class QSpatialAudioSound
- \internal
- */
-
-void QSpatialAudioSound::load()
-{
- decoder.reset(new QAudioDecoder);
- buffers.clear();
- currentBuffer = 0;
- bufPos = 0;
- m_playing = false;
- m_loading = true;
- auto *ep = QSpatialAudioEnginePrivate::get(engine);
- QAudioFormat f = ep->format;
- f.setSampleFormat(QAudioFormat::Float);
- f.setChannelConfig(nchannels == 2 ? QAudioFormat::ChannelConfigStereo : QAudioFormat::ChannelConfigMono);
- decoder->setAudioFormat(f);
- decoder->setSource(url);
-
- connect(decoder.get(), &QAudioDecoder::bufferReady, this, &QSpatialAudioSound::bufferReady);
- connect(decoder.get(), &QAudioDecoder::finished, this, &QSpatialAudioSound::finished);
- decoder->start();
-}
-
-void QSpatialAudioSound::getBuffer(float *buf, int nframes, int channels)
-{
- Q_ASSERT(channels == nchannels);
- QMutexLocker l(&mutex);
- if (!m_playing || currentBuffer >= buffers.size()) {
- memset(buf, 0, nframes*sizeof(float));
- } else {
- int frames = nframes;
- float *ff = buf;
- while (frames) {
- const QAudioBuffer &b = buffers.at(currentBuffer);
-// qDebug() << s << b.format().sampleRate() << b.format().channelCount() << b.format().sampleFormat();
- auto *f = b.constData<float>() + bufPos*nchannels;
- int toCopy = qMin(b.frameCount() - bufPos, frames);
- memcpy(ff, f, toCopy*sizeof(float)*nchannels);
- ff += toCopy*nchannels;
- frames -= toCopy;
- bufPos += toCopy;
- Q_ASSERT(bufPos <= b.frameCount());
- if (bufPos == b.frameCount()) {
- ++currentBuffer;
- bufPos = 0;
- }
- if (!m_loading) {
- if (currentBuffer == buffers.size()) {
- currentBuffer = 0;
- ++m_currentLoop;
- }
- if (m_loops > 0 && m_currentLoop >= m_loops) {
- m_playing = false;
- m_currentLoop = 0;
- }
- }
- }
- Q_ASSERT(ff - buf == channels*nframes);
- }
-}
-
-void QSpatialAudioSound::bufferReady()
-{
- QMutexLocker l(&mutex);
- auto b = decoder->read();
-// qDebug() << "read buffer" << b.format() << b.startTime() << b.duration();
- buffers.append(b);
- if (m_autoPlay)
- m_playing = true;
-}
-
-void QSpatialAudioSound::finished()
-{
-// qDebug() << "finished";
- m_loading = false;
-}
-
-QT_END_NAMESPACE
-
-#include "moc_qspatialaudioengine.cpp"
-#include "qspatialaudioengine.moc"