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