1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-3.0-only
3#include "qambientsound.h"
4#include "qambientsound_p.h"
5#include "qaudioengine_p.h"
6#include "resonance_audio.h"
7#include <qaudiosink.h>
8#include <qurl.h>
9#include <qdebug.h>
10#include <qaudiodecoder.h>
11
12QT_BEGIN_NAMESPACE
13
14void QAmbientSoundPrivate::load()
15{
16 decoder.reset(p: new QAudioDecoder);
17 buffers.clear();
18 currentBuffer = 0;
19 sourceDeviceFile.reset(p: nullptr);
20 bufPos = 0;
21 m_playing = false;
22 m_loading = true;
23 auto *ep = QAudioEnginePrivate::get(engine);
24 QAudioFormat f;
25 f.setSampleFormat(QAudioFormat::Float);
26 f.setSampleRate(ep->sampleRate);
27 f.setChannelConfig(nchannels == 2 ? QAudioFormat::ChannelConfigStereo : QAudioFormat::ChannelConfigMono);
28 decoder->setAudioFormat(f);
29 if (url.scheme().compare(s: u"qrc", cs: Qt::CaseInsensitive) == 0) {
30 auto qrcFile = std::make_unique<QFile>(args: u':' + url.path());
31 if (!qrcFile->open(flags: QFile::ReadOnly))
32 return;
33 sourceDeviceFile = std::move(qrcFile);
34 decoder->setSourceDevice(sourceDeviceFile.get());
35 } else {
36 decoder->setSource(url);
37 }
38 connect(sender: decoder.get(), signal: &QAudioDecoder::bufferReady, context: this, slot: &QAmbientSoundPrivate::bufferReady);
39 connect(sender: decoder.get(), signal: &QAudioDecoder::finished, context: this, slot: &QAmbientSoundPrivate::finished);
40 decoder->start();
41}
42
43void QAmbientSoundPrivate::getBuffer(float *buf, int nframes, int channels)
44{
45 Q_ASSERT(channels == nchannels);
46 QMutexLocker l(&mutex);
47 if (!m_playing || currentBuffer >= buffers.size()) {
48 memset(s: buf, c: 0, n: channels * nframes * sizeof(float));
49 } else {
50 int frames = nframes;
51 float *ff = buf;
52 while (frames) {
53 if (currentBuffer < buffers.size()) {
54 const QAudioBuffer &b = buffers.at(i: currentBuffer);
55 // qDebug() << s << b.format().sampleRate() << b.format().channelCount() << b.format().sampleFormat();
56 auto *f = b.constData<float>() + bufPos*nchannels;
57 int toCopy = qMin(a: b.frameCount() - bufPos, b: frames);
58 memcpy(dest: ff, src: f, n: toCopy*sizeof(float)*nchannels);
59 ff += toCopy*nchannels;
60 frames -= toCopy;
61 bufPos += toCopy;
62 Q_ASSERT(bufPos <= b.frameCount());
63 if (bufPos == b.frameCount()) {
64 ++currentBuffer;
65 bufPos = 0;
66 }
67 } else {
68 // no more data available
69 if (m_loading)
70 qDebug() << "underrun" << frames << "frames when loading" << url;
71 memset(s: ff, c: 0, n: frames * channels * sizeof(float));
72 ff += frames * channels;
73 frames = 0;
74 }
75 if (!m_loading) {
76 if (currentBuffer == buffers.size()) {
77 currentBuffer = 0;
78 ++m_currentLoop;
79 }
80 if (m_loops > 0 && m_currentLoop >= m_loops) {
81 m_playing = false;
82 m_currentLoop = 0;
83 }
84 }
85 }
86 Q_ASSERT(ff - buf == channels*nframes);
87 }
88}
89
90void QAmbientSoundPrivate::bufferReady()
91{
92 QMutexLocker l(&mutex);
93 auto b = decoder->read();
94 // qDebug() << "read buffer" << b.format() << b.startTime() << b.duration();
95 buffers.append(t: b);
96 if (m_autoPlay)
97 m_playing = true;
98}
99
100void QAmbientSoundPrivate::finished()
101{
102 m_loading = false;
103}
104
105/*!
106 \class QAmbientSound
107 \inmodule QtSpatialAudio
108 \ingroup spatialaudio
109 \ingroup multimedia_audio
110
111 \brief A stereo overlay sound.
112
113 QAmbientSound represents a position and orientation independent sound.
114 It's commonly used for background sounds (e.g. music) that is supposed to be independent
115 of the listeners position and orientation.
116 */
117
118/*!
119 Creates a stereo sound source for \a engine.
120 */
121QAmbientSound::QAmbientSound(QAudioEngine *engine)
122 : d(new QAmbientSoundPrivate(this))
123{
124 setEngine(engine);
125}
126
127QAmbientSound::~QAmbientSound()
128{
129 setEngine(nullptr);
130 delete d;
131}
132
133/*!
134 \property QAmbientSound::volume
135
136 Defines the volume of the sound.
137
138 Values between 0 and 1 will attenuate the sound, while values above 1
139 provide an additional gain boost.
140 */
141void QAmbientSound::setVolume(float volume)
142{
143 if (d->volume == volume)
144 return;
145 d->volume = volume;
146 auto *ep = QAudioEnginePrivate::get(engine: d->engine);
147 if (ep)
148 ep->resonanceAudio->api->SetSourceVolume(source_id: d->sourceId, volume: d->volume);
149 emit volumeChanged();
150}
151
152float QAmbientSound::volume() const
153{
154 return d->volume;
155}
156
157void QAmbientSound::setSource(const QUrl &url)
158{
159 if (d->url == url)
160 return;
161 d->url = url;
162
163 d->load();
164 emit sourceChanged();
165}
166
167/*!
168 \property QAmbientSound::source
169
170 The source file for the sound to be played.
171 */
172QUrl QAmbientSound::source() const
173{
174 return d->url;
175}
176/*!
177 \enum QAmbientSound::Loops
178
179 Lets you control the playback loop using the following values:
180
181 \value Infinite Loops infinitely
182 \value Once Stops playback after running once
183*/
184/*!
185 \property QAmbientSound::loops
186
187 Determines how many times the sound is played before the player stops.
188 Set to QAmbientSound::Infinite to play the current sound in
189 a loop forever.
190
191 The default value is \c 1.
192 */
193int QAmbientSound::loops() const
194{
195 return d->m_loops.loadRelaxed();
196}
197
198void QAmbientSound::setLoops(int loops)
199{
200 int oldLoops = d->m_loops.fetchAndStoreRelaxed(newValue: loops);
201 if (oldLoops != loops)
202 emit loopsChanged();
203}
204
205/*!
206 \property QAmbientSound::autoPlay
207
208 Determines whether the sound should automatically start playing when a source
209 gets specified.
210
211 The default value is \c true.
212 */
213bool QAmbientSound::autoPlay() const
214{
215 return d->m_autoPlay.loadRelaxed();
216}
217
218void QAmbientSound::setAutoPlay(bool autoPlay)
219{
220 bool old = d->m_autoPlay.fetchAndStoreRelaxed(newValue: autoPlay);
221 if (old != autoPlay)
222 emit autoPlayChanged();
223}
224
225/*!
226 Starts playing back the sound. Does nothing if the sound is already playing.
227 */
228void QAmbientSound::play()
229{
230 d->play();
231}
232
233/*!
234 Pauses sound playback. Calling play() will continue playback.
235 */
236void QAmbientSound::pause()
237{
238 d->pause();
239}
240
241/*!
242 Stops sound playback and resets the current position and current loop count to 0.
243 Calling play() will start playback at the beginning of the sound file.
244 */
245void QAmbientSound::stop()
246{
247 d->stop();
248}
249
250/*!
251 \internal
252 */
253void QAmbientSound::setEngine(QAudioEngine *engine)
254{
255 if (d->engine == engine)
256 return;
257
258 // Remove self from old engine (if necessary)
259 auto *ep = QAudioEnginePrivate::get(engine: d->engine);
260 if (ep)
261 ep->removeStereoSound(sound: this);
262
263 d->engine = engine;
264
265 // Add self to new engine if necessary
266 ep = QAudioEnginePrivate::get(engine: d->engine);
267 if (ep) {
268 ep->addStereoSound(sound: this);
269 ep->resonanceAudio->api->SetSourceVolume(source_id: d->sourceId, volume: d->volume);
270 }
271}
272
273/*!
274 Returns the engine associated with this sound.
275 */
276QAudioEngine *QAmbientSound::engine() const
277{
278 return d->engine;
279}
280
281QT_END_NAMESPACE
282
283#include "moc_qambientsound.cpp"
284

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtmultimedia/src/spatialaudio/qambientsound.cpp