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