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/qfile.h>
8#include <QtCore/qurl.h>
9#include <QtMultimedia/qaudiodecoder.h>
10#include <QtMultimedia/qaudiosink.h>
11#include <QtMultimedia/private/qaudio_qspan_support_p.h>
12#include <QtMultimedia/private/qmultimedia_ranges_p.h>
13#include <QtSpatialAudio/private/qambientsound_p.h>
14#include <QtSpatialAudio/private/qaudioengine_p.h>
15
16#include <resonance_audio.h>
17#include <memory>
18
19QT_BEGIN_NAMESPACE
20
21QAmbientSoundPrivate::QAmbientSoundPrivate(QAudioEngine *engine, int nchannels)
22 : nchannels(nchannels), engine(engine)
23{
24}
25
26QAmbientSoundPrivate::~QAmbientSoundPrivate() = default;
27
28void QAmbientSoundPrivate::setVolume(float volume)
29{
30 m_volume = volume;
31 applyVolume();
32}
33
34void QAmbientSoundPrivate::applyVolume()
35{
36 auto *ep = QAudioEnginePrivate::get(engine);
37 if (ep)
38 ep->resonanceAudio->api->SetSourceVolume(sourceId, m_volume);
39}
40
41void QAmbientSoundPrivate::loadUrl(const QUrl &url)
42{
43 m_url = url;
44
45 Q_Q(QAmbientSound);
46
47 {
48 QMutexLocker l(&mutex);
49 buffers.clear();
50 currentBuffer = 0;
51 bufPos = 0;
52 m_currentLoop = 0;
53 m_playing = false;
54 }
55 m_loadFuture.cancel();
56
57 auto *ep = QAudioEnginePrivate::get(engine);
58 QAudioFormat f;
59 f.setSampleFormat(QAudioFormat::Float);
60 f.setSampleRate(ep->sampleRate());
61 f.setChannelConfig(nchannels == 2 ? QAudioFormat::ChannelConfigStereo
62 : QAudioFormat::ChannelConfigMono);
63
64 QUrl resolved = m_sourceResolver->resolve(url);
65 m_loadFuture = load(resolved, f).then(q, [this](QAmbientSoundPrivate::LoadResult result) {
66 QMutexLocker l(&mutex);
67 if (result) {
68 buffers = std::move(*result);
69 if (m_autoPlay)
70 m_playing = true;
71 } else {
72 qWarning() << "QAmbientSound: cannot load file";
73 }
74 });
75}
76
77auto QAmbientSoundPrivate::load(QUrl resolvedUrl, QAudioFormat format) -> QFuture<LoadResult>
78{
79 m_loadFuture.cancelChain();
80
81 auto promise = std::make_shared<QPromise<LoadResult>>();
82 auto future = promise->future();
83
84 m_decoder = std::make_unique<QAudioDecoder>();
85 m_decoder->setAudioFormat(format);
86
87 std::shared_ptr<QIODevice> file; // kept alive until decoding is finished
88 if (resolvedUrl.scheme().compare(u"qrc", Qt::CaseInsensitive) == 0) {
89 file = std::make_unique<QFile>(u':' + resolvedUrl.path());
90 if (!file->open(QFile::ReadOnly)) {
91 promise->start();
92 promise->addResult(q23::unexpected{ QAudioDecoder::Error::ResourceError });
93 promise->finish();
94
95 m_decoder = {};
96
97 return future;
98 }
99 m_decoder->setSourceDevice(file.get());
100 } else {
101 m_decoder->setSource(resolvedUrl);
102 }
103
104 auto accum = std::make_shared<QList<QAudioBuffer>>();
105 QObject::connect(m_decoder.get(), &QAudioDecoder::bufferReady, m_decoder.get(), [accum, this] {
106 accum->append(m_decoder->read());
107 });
108
109 QObject::connect(
110 m_decoder.get(),
111 static_cast<void (QAudioDecoder::*)(QAudioDecoder::Error)>(&QAudioDecoder::error),
112 m_decoder.get(), [promise, file](QAudioDecoder::Error error) {
113 promise->start();
114 promise->addResult(q23::unexpected{ error });
115 promise->finish();
116 });
117
118 QObject::connect(m_decoder.get(), &QAudioDecoder::finished, m_decoder.get(),
119 [promise, file, accum] {
120 promise->start();
121 promise->addResult(std::move(*accum));
122 promise->finish();
123 });
124
125 m_decoder->start();
126 return future;
127}
128
129void QAmbientSoundPrivate::getBuffer(QSpan<float> output, int nframes, int channels)
130{
131 Q_ASSERT(channels == nchannels);
132 Q_ASSERT(output.size() == channels * nframes);
133
134 QMutexLocker l(&mutex);
135 namespace ranges = QtMultimediaPrivate::ranges;
136
137 if (!m_playing || currentBuffer >= buffers.size()) {
138 ranges::fill(output, 0.f);
139 } else {
140 using QtMultimediaPrivate::drop;
141 using QtMultimediaPrivate::take;
142
143 int frames = nframes;
144 while (frames) {
145 if (currentBuffer < buffers.size()) {
146 const QAudioBuffer &b = buffers.at(currentBuffer);
147 const float *sourceData = b.constData<float>() + bufPos * nchannels;
148
149 // Copy frames
150 int framesToCopy = qMin(b.frameCount() - bufPos, frames);
151 QSpan<const float> source(sourceData, framesToCopy * nchannels);
152 QSpan<float> destination = take(output, framesToCopy * nchannels);
153 std::copy(source.begin(), source.end(), destination.begin());
154
155 // Advance output span
156 output = drop(output, framesToCopy * nchannels);
157
158 frames -= framesToCopy;
159 bufPos += framesToCopy;
160 Q_ASSERT(bufPos <= b.frameCount());
161
162 if (bufPos == b.frameCount()) {
163 ++currentBuffer;
164 bufPos = 0;
165 }
166 } else {
167 // Fill remaining with silence
168 ranges::fill(output, 0.f);
169 return;
170 }
171
172 if (currentBuffer == buffers.size()) {
173 currentBuffer = 0;
174 ++m_currentLoop;
175 }
176 if (m_loops > 0 && m_currentLoop >= m_loops) {
177 m_playing = false;
178 m_currentLoop = 0;
179 }
180 }
181 Q_ASSERT(output.size() == 0);
182 }
183}
184
185/*!
186 \class QAmbientSound
187 \inmodule QtSpatialAudio
188 \ingroup spatialaudio
189 \ingroup multimedia_audio
190
191 \brief A stereo overlay sound.
192
193 QAmbientSound represents a position and orientation independent sound.
194 It's commonly used for background sounds (e.g. music) that is supposed to be independent
195 of the listeners position and orientation.
196 */
197
198/*!
199 Creates a stereo sound source for \a engine.
200 */
201QAmbientSound::QAmbientSound(QAudioEngine *engine) : QObject(*new QAmbientSoundPrivate(engine))
202{
203 Q_D(QAmbientSound);
204
205 auto *ep = QAudioEnginePrivate::get(d->engine);
206 if (ep) {
207 ep->addStereoSound(this);
208 d->applyVolume();
209 }
210}
211
212QAmbientSound::~QAmbientSound()
213{
214 Q_D(QAmbientSound);
215
216 auto *ep = QAudioEnginePrivate::get(d->engine);
217 if (ep)
218 ep->removeStereoSound(this);
219}
220
221/*!
222 \property QAmbientSound::volume
223
224 Defines the volume of the sound.
225
226 Values between 0 and 1 will attenuate the sound, while values above 1
227 provide an additional gain boost.
228 */
229void QAmbientSound::setVolume(float volume)
230{
231 Q_D(QAmbientSound);
232 if (volume != d->volume()) {
233 d->setVolume(volume);
234 emit volumeChanged();
235 }
236}
237
238float QAmbientSound::volume() const
239{
240 Q_D(const QAmbientSound);
241 return d->volume();
242}
243
244void QAmbientSound::setSource(const QUrl &url)
245{
246 Q_D(QAmbientSound);
247 if (d->url() == url)
248 return;
249 d->loadUrl(url);
250
251 emit sourceChanged();
252}
253
254/*!
255 \property QAmbientSound::source
256
257 The source file for the sound to be played.
258 */
259QUrl QAmbientSound::source() const
260{
261 Q_D(const QAmbientSound);
262 return d->url();
263}
264/*!
265 \enum QAmbientSound::Loops
266
267 Lets you control the playback loop using the following values:
268
269 \value Infinite Loops infinitely
270 \value Once Stops playback after running once
271*/
272/*!
273 \property QAmbientSound::loops
274
275 Determines how many times the sound is played before the player stops.
276 Set to QAmbientSound::Infinite to play the current sound in
277 a loop forever.
278
279 The default value is \c 1.
280 */
281int QAmbientSound::loops() const
282{
283 Q_D(const QAmbientSound);
284 return d->m_loops.load(std::memory_order_relaxed);
285}
286
287void QAmbientSound::setLoops(int loops)
288{
289 Q_D(QAmbientSound);
290 int oldLoops = d->m_loops.exchange(loops, std::memory_order_relaxed);
291 if (oldLoops != loops)
292 emit loopsChanged();
293}
294
295/*!
296 \property QAmbientSound::autoPlay
297
298 Determines whether the sound should automatically start playing when a source
299 gets specified.
300
301 The default value is \c true.
302 */
303bool QAmbientSound::autoPlay() const
304{
305 Q_D(const QAmbientSound);
306 return d->m_autoPlay.load(std::memory_order_relaxed);
307}
308
309void QAmbientSound::setAutoPlay(bool autoPlay)
310{
311 Q_D(QAmbientSound);
312
313 bool old = d->m_autoPlay.exchange(autoPlay, std::memory_order_relaxed);
314 if (old != autoPlay)
315 emit autoPlayChanged();
316}
317
318/*!
319 Starts playing back the sound. Does nothing if the sound is already playing.
320 */
321void QAmbientSound::play()
322{
323 Q_D(QAmbientSound);
324 d->play();
325}
326
327/*!
328 Pauses sound playback. Calling play() will continue playback.
329 */
330void QAmbientSound::pause()
331{
332 Q_D(QAmbientSound);
333 d->pause();
334}
335
336/*!
337 Stops sound playback and resets the current position and current loop count to 0.
338 Calling play() will start playback at the beginning of the sound file.
339 */
340void QAmbientSound::stop()
341{
342 Q_D(QAmbientSound);
343 d->stop();
344}
345
346/*!
347 Returns the engine associated with this sound.
348 */
349QAudioEngine *QAmbientSound::engine() const
350{
351 Q_D(const QAmbientSound);
352
353 return d->engine;
354}
355
356QT_END_NAMESPACE
357
358#include "moc_qambientsound.cpp"