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