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