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
qspatialaudiosound.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 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 <QtSpatialAudio/private/qaudioengine_p.h>
7#include <QtMultimedia/qaudiosink.h>
8#include <QtMultimedia/private/qaudio_qspan_support_p.h>
9#include <QtMultimedia/private/qaudiohelpers_p.h>
10#include <QtMultimedia/private/qmultimedia_ranges_p.h>
11#include <QtMultimedia/private/qmultimediautils_p.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qurl.h>
14
15#include <resonance_audio.h>
16
17#include <memory>
18
19QT_BEGIN_NAMESPACE
20
21namespace QSpatialAudioPrivate {
22
23constexpr int InfiniteLoops = -1;
24
25QSpatialAudioPlaybackState::QSpatialAudioPlaybackState(QAudioBuffer buffer, bool playing, int loops)
27{
28}
29
30void QSpatialAudioPlaybackState::getBuffer(QSpan<float> output)
31{
32 using QtMultimediaPrivate::drop;
33 using QtMultimediaPrivate::take;
34 namespace ranges = QtMultimediaPrivate::ranges;
35
36 if (!m_playing.load(std::memory_order_relaxed)) {
37 ranges::fill(output, 0.f);
38 return;
39 }
40
41 QSpan<const float> wholeSampleBuffer{
42 m_buffer.constData<float>(),
43 m_buffer.sampleCount(),
44 };
45
46 while (!output.empty()) {
47 QSpan remainingSamples = drop(wholeSampleBuffer, m_currentSample);
48 const QSpan samplesToCopy = take(remainingSamples, output.size());
49 ranges::copy(samplesToCopy, output.begin());
50 output = drop(output, samplesToCopy.size());
51 m_currentSample += samplesToCopy.size();
52
53 if (output.empty())
54 return;
55
56 // Reached end of buffer
57 Q_ASSERT(m_currentSample == wholeSampleBuffer.size());
58 m_currentSample = 0;
59
60 switch (m_loops) {
61 case InfiniteLoops:
62 break; // Continue looping
63 case 0:
64 m_playing = false;
65 m_currentLoop = 0;
66 ranges::fill(output, 0.f);
67 return;
68 default:
69 ++m_currentLoop;
70 if (m_currentLoop >= m_loops) {
71 m_playing = false;
72 m_currentLoop = 0;
73 ranges::fill(output, 0.f);
74 return;
75 }
76 break;
77 }
78 }
79}
80
82{
83 m_playing = true;
84}
85
87{
88 m_loops = loops;
89}
90
92{
93 return m_buffer.format();
94}
95
97{
98 m_playing = false;
99}
100
101} // namespace QSpatialAudioPrivate
102
103QSpatialAudioSoundPrivate::QSpatialAudioSoundPrivate(QAudioEngine *engine, int nchannels, int sourceId)
104 : nchannels(nchannels), engine(engine), sourceId(sourceId)
105{
106 auto *ep = QAudioEnginePrivate::get(engine);
107 if (!ep)
108 return;
109 ep->addSound(this);
110}
111
113{
114 auto *ep = QAudioEnginePrivate::get(engine);
115 if (ep)
116 ep->removeSound(this);
117
118 withResonanceApi([&](vraudio::ResonanceAudioApi *api) {
119 api->DestroySource(sourceId);
120 });
121}
122
124{
125 m_volume = volume;
127}
128
130{
131 m_loops = loops;
132 if (m_playbackState)
133 m_playbackState->setLoops(loops);
134}
135
137{
138 m_autoPlay = enabled;
139}
140
142{
143 withResonanceApi([&](vraudio::ResonanceAudioApi *api) {
144 api->SetSourceVolume(sourceId, m_volume);
145 });
146}
147
149{
150 qCDebug(qLcSpatialAudioEngine) << "Playing sound";
151 switch (m_state) {
152 case State::Stopped: {
153 m_state = State::Playing;
154 if (m_buffer) {
155 setState(std::make_shared<QSpatialAudioPlaybackState>(*m_buffer, /*playing=*/true,
156 m_loops));
157 }
158 return;
159 }
160 case State::Paused:
161 case State::Playing: {
162 m_state = State::Playing;
163 if (m_playbackState)
164 m_playbackState->resume();
165 return;
166 }
167 }
168}
169
171{
172 qCDebug(qLcSpatialAudioEngine) << "Pausing sound";
173 switch (m_state) {
174 case State::Stopped: {
175 m_state = State::Paused;
176 setState(std::make_shared<QSpatialAudioPlaybackState>(*m_buffer, /*playing=*/false,
177 m_loops));
178 return;
179 }
180 case State::Paused: {
181 return;
182 }
183 case State::Playing: {
184 m_state = State::Paused;
185 setState(nullptr);
186
187 if (m_playbackState)
188 m_playbackState->pause();
189 return;
190 }
191 }
192}
193
195{
196 qCDebug(qLcSpatialAudioEngine) << "Stopping sound";
197 switch (m_state) {
198 case State::Stopped: {
199 return;
200 }
201 case State::Paused:
202 case State::Playing: {
203 m_state = State::Stopped;
204 setState({});
205 return;
206 }
207 }
208}
209
210void QSpatialAudioSoundPrivate::loadUrl(const QUrl &url)
211{
212 qCDebug(qLcSpatialAudioEngine) << "Loading sound:" << url;
213 m_url = url;
214
215 auto *ep = QAudioEnginePrivate::get(engine);
216 if (!ep)
217 return;
218
219 m_loadFuture.cancelChain();
220 m_sample = {};
221
222 setState(nullptr);
223
224 m_loadFuture = QSampleCache::instance()
225 ->requestSampleFuture(m_sourceResolver->resolve(url), ep->sampleRate())
226 .then(q_ptr, [this](SharedSamplePtr sample) {
227 if (!sample) {
228 qCWarning(qLcSpatialAudioEngine) << "QAmbientSound: cannot load file";
229 return;
230 }
231
232 // Build QAudioBuffer with the correct channel layout for this source.
233 // QSampleCache resampled to engineRate but preserves native channels.
234 // Adapt channel count here: mono<->stereo simple mix.
235 const QAudioFormat &srcFmt = sample->format();
236 const int srcChannels = srcFmt.channelCount();
237
238 QByteArray pcmData;
239 QAudioFormat outFmt = srcFmt;
240 outFmt.setChannelCount(nchannels);
241 outFmt.setChannelConfig(nchannels == 2 ? QAudioFormat::ChannelConfigStereo
242 : QAudioFormat::ChannelConfigMono);
243
244 using namespace QAudioHelperInternal;
245
246 if (srcChannels == nchannels) {
247 pcmData = sample->data();
248
249 m_sample = std::move(sample); // keep the sample in the QSampleCache as long as we live
250 } else if (srcChannels == 1 && nchannels == 2) {
251 qWarning() << "QAmbientSound: upmixing mono source to stereo";
252 pcmData = upmixMonoToStereo(sample->dataAsFloatSpan(), UpmixScaling::Duplicate);
253 } else if (srcChannels == 2 && nchannels == 1) {
254 qWarning() << "QAmbientSound: downmixing stereo source to mono";
255 pcmData = downmixStereoToMono(sample->dataAsFloatSpan(), DownmixScaling::Average);
256 } else {
257 qWarning() << "QAmbientSound: unsupported channel count"
258 << srcChannels << "- rejecting file";
259 return;
260 }
261
262 m_buffer = QAudioBuffer{
263 pcmData,
264 outFmt,
265 };
266
267 qCDebug(qLcSpatialAudioEngine) << "Sound loaded";
268
269 const bool startingPlayback = m_autoPlay || m_state != State::Stopped;
270 if (!startingPlayback)
271 return;
272 const bool startPaused = (m_state == State::Paused) && !m_autoPlay;
273 setState(std::make_shared<QSpatialAudioPlaybackState>(*m_buffer, /*playing=*/!startPaused,
274 m_loops));
275
276 m_state = startPaused ? State::Paused : State::Playing;
277 qCDebug(qLcSpatialAudioEngine)
278 << "Sound playback started:" << (startPaused ? "paused" : "playing");
279 });
280}
281
282vraudio::ResonanceAudioApi *QSpatialAudioSoundPrivate::getAPI()
283{
284 auto *ep = QAudioEnginePrivate::get(engine);
285 if (!ep)
286 return nullptr;
287 return ep->resonanceAudio->api.get();
288}
289
290void QSpatialAudioSoundPrivate::setState(SharedPlaybackState state)
291{
292 auto *ep = QAudioEnginePrivate::get(engine);
293 if (ep)
294 ep->setSoundPlaybackData(this, state);
295 m_playbackState = std::move(state);
296}
297
298QT_END_NAMESPACE
QSpatialAudioPlaybackState(QAudioBuffer buffer, bool playing, int loops)
void loadUrl(const QUrl &url)
QSpatialAudioSoundPrivate(QAudioEngine *engine, int nchannels, int sourceId)