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