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
qaudioengine_withplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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/qmediadevices.h>
7#include <QtMultimedia/private/q_pmr_emulation_p.h>
8#include <QtMultimedia/private/qmemory_resource_tlsf_p.h>
9#include <QtMultimedia/private/qmultimedia_ranges_p.h>
10#include <QtMultimedia/private/qrtaudioengine_p.h>
11#include <QtSpatialAudio/private/qambientsound_p.h>
12#include <QtSpatialAudio/private/qambisonicdecoder_p.h>
13#include <QtSpatialAudio/private/qspatialsound_p.h>
14
15#include <resonance_audio.h>
16
17QT_BEGIN_NAMESPACE
18
19enum class SourceId : int { };
20
22{
23public:
25
27
28 /// QRtAudioEngineVoice overrides
30 bool isActive() noexcept QT_MM_NONBLOCKING override { return true; }
31 const QAudioFormat &format() noexcept override { return m_format; }
32
35 void removeSound(SourceId);
36
37 void setPaused(bool);
38 void setOutputMode(QAudioEngine::OutputMode mode);
39
40private:
41 void processSlice(QSpan<float> hostBuffer);
42 void processSliceFillResonanceBuffers();
43 void processSliceProcessSurroundMode(QSpan<float>);
44 void processSliceProcessWithoutReverb(QSpan<float>);
45
46 constexpr static auto framesPerBuffer = QAudioEnginePrivate::framesPerBuffer;
47 QAudioEngine::OutputMode m_outputMode;
48 const std::shared_ptr<vraudio::ResonanceAudio> m_resonanceAudio;
49 const QAudioFormat m_format;
50 QAmbisonicDecoder m_ambisonicDecoder;
51 bool m_paused = false;
52
53 struct SoundState
54 {
55 SharedPlaybackState playbackState;
56 int numberOfChannels;
57 };
58
59 static constexpr size_t poolSize =
61 * 512 // space for 512 sounds
62 + sizeof(float) * qToUnderlying(framesPerBuffer) * 32 // leftover buffer for 32 channels
63 + sizeof(float) * qToUnderlying(framesPerBuffer) * 32 // temporary slice buffer
64 + 16384; // some extra space for internal fragmentation and allocator metadata
65 QtMultimediaPrivate::QTlsfMemoryResource m_rtMemoryPool{ poolSize };
66
67 QtMultimediaPrivate::pmr::map<SourceId, SoundState> m_sounds{ &m_rtMemoryPool };
68 QtMultimediaPrivate::pmr::vector<float> m_leftoverBuffer{ &m_rtMemoryPool };
69};
70
84
86QResonanceAudioPlayer::play(QSpan<float> outputBuffer) noexcept
87{
88 using namespace QtMultimediaPrivate;
89 namespace ranges = QtMultimediaPrivate::ranges;
90
91 if (m_paused) {
92 ranges::fill(outputBuffer, 0);
93 return VoicePlayResult::Playing;
94 }
95
96 const size_t samplesPerSlice = m_format.channelCount() * qToUnderlying(framesPerBuffer);
97
98 while (!outputBuffer.empty()) {
99 if (!m_leftoverBuffer.empty()) {
100 // we have leftovers
101 QSpan leftoverSpanToCopy = take(QSpan(m_leftoverBuffer), outputBuffer.size());
102 ranges::copy(leftoverSpanToCopy, outputBuffer.begin());
103 m_leftoverBuffer.erase(m_leftoverBuffer.begin(),
104 m_leftoverBuffer.begin() + leftoverSpanToCopy.size());
105 outputBuffer = drop(outputBuffer, leftoverSpanToCopy.size());
106 continue;
107 }
108
109 Q_ASSERT(m_leftoverBuffer.empty());
110
111 auto sliceBuffer = take(outputBuffer, samplesPerSlice);
112 size_t sliceBufferSize = sliceBuffer.size();
113
114 if (sliceBufferSize == samplesPerSlice) {
115 // normal case: we have enough space
116 processSlice(sliceBuffer);
117 outputBuffer = drop(outputBuffer, samplesPerSlice);
118 continue;
119 } else {
120 // not enough space to fill a whole slice.
121 pmr::vector<float> sliceBuffer{
122 samplesPerSlice,
123 0.0f,
124 pmr::vector<float>::allocator_type{ m_leftoverBuffer.get_allocator().resource() },
125 };
126 processSlice(sliceBuffer);
127
128 QSpan sliceToOutput = take(QSpan(sliceBuffer), sliceBufferSize);
129 QSpan sliceToKeep = drop(QSpan(sliceBuffer), sliceBufferSize);
130 ranges::copy(sliceToOutput, outputBuffer.begin());
131 m_leftoverBuffer.assign(sliceToKeep.begin(), sliceToKeep.end());
132
133 outputBuffer = drop(outputBuffer, sliceToOutput.size());
134 }
135 }
136
137 return VoicePlayResult::Playing;
138}
139
140void QResonanceAudioPlayer::addSound(SourceId id, int numberOfChannels, SharedPlaybackState state)
141{
142 auto [_, inserted] = m_sounds.insert_or_assign(id,
143 SoundState{
144 std::move(state),
145 numberOfChannels,
146 });
147 Q_ASSERT(inserted);
148}
149
150void QResonanceAudioPlayer::setSoundPlaybackData(SourceId id, SharedPlaybackState state)
151{
152 auto it = m_sounds.find(id);
153 Q_ASSERT(it != m_sounds.end());
154 it->second.playbackState = std::move(state);
155}
156
158{
159 m_sounds.erase(sound);
160}
161
163{
164 m_paused = paused;
165}
166
167void QResonanceAudioPlayer::setOutputMode(QAudioEngine::OutputMode mode)
168{
169 m_outputMode = mode;
170}
171
172void QResonanceAudioPlayer::processSlice(QSpan<float> hostBuffer)
173{
174 using QtMultimediaPrivate::drop;
175 constexpr auto framesPerBuffer = qToUnderlying(QAudioEnginePrivate::framesPerBuffer);
176 const qsizetype samplesPerSlice = m_format.channelCount() * framesPerBuffer;
177
178 Q_ASSERT(hostBuffer.size() == samplesPerSlice);
179
180 // fill host API
181 processSliceFillResonanceBuffers();
182
183 // and process
184 switch (m_outputMode) {
185 case QAudioEngine::Surround:
186 processSliceProcessSurroundMode(hostBuffer);
187 break;
188 default:
189 processSliceProcessWithoutReverb(hostBuffer);
190 break;
191 }
192}
193
194
195void QResonanceAudioPlayer::processSliceFillResonanceBuffers()
196{
197 constexpr auto framesPerBuffer = qToUnderlying(QAudioEnginePrivate::framesPerBuffer);
198 static constexpr std::array<float, 2 * framesPerBuffer> nullBuffer{};
199 using QtMultimediaPrivate::take;
200 vraudio::ResonanceAudioApi *api = m_resonanceAudio->api.get();
201
202 for (auto &&[sourceId, sourceState] : m_sounds) {
203 int numberOfChannels = sourceState.numberOfChannels;
204 SharedPlaybackState &playbackState = sourceState.playbackState;
205 if (playbackState) {
206 Q_ASSERT(playbackState->format().channelCount() <= 2);
207 std::array<float, 2 * framesPerBuffer> buf;
208
209 playbackState->getBuffer(take(
210 QSpan<float>{ buf }, playbackState->format().channelCount() * framesPerBuffer));
211 api->SetInterleavedBuffer(qToUnderlying(sourceId), buf.data(), numberOfChannels,
212 framesPerBuffer);
213 } else {
214 api->SetInterleavedBuffer(qToUnderlying(sourceId), nullBuffer.data(), numberOfChannels,
215 framesPerBuffer);
216 }
217 }
218}
219
220void QResonanceAudioPlayer::processSliceProcessSurroundMode(QSpan<float> outputBuffer)
221{
222 constexpr auto framesPerBuffer = qToUnderlying(QAudioEnginePrivate::framesPerBuffer);
223 using QtMultimediaPrivate::take;
224 namespace ranges = QtMultimediaPrivate::ranges;
225 Q_ASSERT(outputBuffer.size() == m_format.channelCount() * framesPerBuffer);
226
227 std::array<const float *, QAmbisonicDecoder::maxAmbisonicChannels> channels;
228 std::array<const float *, 2> reverbBuffers{};
229 int nFrames = m_resonanceAudio->getAmbisonicOutput(channels.data(), reverbBuffers.data(),
230 m_ambisonicDecoder.nInputChannels());
231
232 if (nFrames < 0) {
233 // If we get here, it means that resonanceAudio did not actually fill the buffer.
234 // Sometimes this is expected, for example if resonanceAudio does not have any sources.
235 // In this case we just fill the buffer with silence.
236 ranges::fill(outputBuffer, 0);
237 return;
238 }
239 auto nSamples = m_ambisonicDecoder.outputSamples(nFrames);
240
241 Q_ASSERT(m_ambisonicDecoder.nOutputChannels() <= 8);
242 QSpan currentOutput = take(outputBuffer, nSamples);
243 m_ambisonicDecoder.processBufferWithReverb(
244 QSpan{ channels.data(), m_ambisonicDecoder.nInputChannels() }, reverbBuffers,
245 currentOutput);
246}
247
248void QResonanceAudioPlayer::processSliceProcessWithoutReverb(QSpan<float> outputBuffer)
249{
250 constexpr auto framesPerBuffer = qToUnderlying(QAudioEnginePrivate::framesPerBuffer);
251
252 Q_ASSERT(outputBuffer.size() == m_format.channelCount() * framesPerBuffer);
253 namespace ranges = QtMultimediaPrivate::ranges;
254
255 bool ok = m_resonanceAudio->api->FillInterleavedOutputBuffer(
256 m_format.channelCount(), framesPerBuffer, outputBuffer.data());
257 if (!ok) {
258 // If we get here, it means that resonanceAudio did not actually fill the buffer.
259 // Sometimes this is expected, for example if resonanceAudio does not have any sources.
260 // In this case we just fill the buffer with silence.
261 if (m_sounds.empty()) {
262 ranges::fill(outputBuffer, 0.f);
263 } else {
264 // If we get here, it means that something unexpected happened, so bail.
265 qWarning() << " processSliceProcessWithoutReverb failure!";
266 return;
267 }
268 }
269}
270
271///////////////////////////////////////////////////////////////////////////////////////////////////
272
276
277QAudioEngineWithPlayer::~QAudioEngineWithPlayer()
278{
279 stop();
280 m_playbackEngine = nullptr;
281}
282
284{
285 QAudioDevice device = m_device;
286 if (device.isNull())
287 device = QMediaDevices::defaultAudioOutput();
288
289 if (device.isNull()) {
290 qWarning() << "QAudioEngine::start() failed: No audio output device found.";
291 return;
292 }
293
294 QAudioFormat format;
295 format.setSampleFormat(QAudioFormat::Float);
296 format.setSampleRate(sampleRate());
297 format.setChannelCount(std::min(2, device.maximumChannelCount()));
298
299 m_playbackEngine = std::make_shared<QRtAudioEngine>(device, format, std::nullopt,
300 QAudioEnginePrivate::framesPerBuffer);
301 m_format = format;
302
303 auto voice = std::make_shared<QResonanceAudioPlayer>(this, QRtAudioEngine::allocateVoiceId());
304
305 for (auto &[sound, state] : playbackStates)
306 voice->addSound(SourceId{ sound->sourceId }, sound->nchannels, state);
307
308 m_engineVoice = voice;
309 m_playbackEngine->play(voice);
310}
311
313{
314 if (!m_engineVoice)
315 return;
316 m_playbackEngine->stop(m_engineVoice);
317 m_engineVoice = {};
318}
319
321{
322 if (paused == m_paused)
323 return;
324 m_paused = paused;
325 if (!m_engineVoice)
326 return;
327
328 // CHECK: can we pause the stream? it seems a bit problematic, as it would prevent us from sending
329 // rt-visitors to update the state of the player.
330 m_playbackEngine->visitVoiceRt(m_engineVoice->voiceId(),
331 [paused](QResonanceAudioPlayer &player) {
332 player.setPaused(paused);
333 });
334}
335
337{
338 return m_paused;
339}
340
341void QAudioEngineWithPlayer::setOutputDevice(const QAudioDevice &device)
342{
343 if (m_device == device)
344 return;
345 if (m_engineVoice) {
346 qWarning() << "Changing device on a running engine not implemented";
347 return;
348 }
349 m_device = device;
350 Q_Q(QAudioEngine);
351 emit q->outputDeviceChanged();
352}
353
355{
356 return m_device;
357}
358
359void QAudioEngineWithPlayer::addSound(QAmbientSoundPrivate *sound)
360{
361 playbackStates.emplace(sound, nullptr);
362
363 if (!m_engineVoice)
364 return;
365
366 m_playbackEngine->visitVoiceRt(
367 m_engineVoice->voiceId(),
368 [id = SourceId{ sound->sourceId },
369 numberOfChannels = sound->nchannels](QResonanceAudioPlayer &player) mutable {
370 player.addSound(id, numberOfChannels);
371 });
372}
373
374void QAudioEngineWithPlayer::removeSound(QAmbientSoundPrivate *sound)
375{
376 SharedPlaybackState oldState = std::move(playbackStates[sound]);
377 playbackStates.erase(sound);
378 if (!m_engineVoice)
379 return;
380
381 // pass oldState to the lambda to ensure that it is not freed on the real-time thread
382 m_playbackEngine->visitVoiceRt(m_engineVoice->voiceId(),
383 [id = SourceId{ sound->sourceId },
384 oldState = std::move(oldState)](QResonanceAudioPlayer &player) {
385 player.removeSound(id);
386 });
387}
388
389void QAudioEngineWithPlayer::setSoundPlaybackData(QAmbientSoundPrivate *sound,
390 SharedPlaybackState state)
391{
392 auto it = playbackStates.find(sound);
393 Q_ASSERT(it != playbackStates.end());
394
395 SharedPlaybackState oldState = std::exchange(it->second, state);
396
397 if (!m_engineVoice)
398 return;
399
400 // pass oldState to the lambda to ensure that it is not freed on the real-time thread
401 m_playbackEngine->visitVoiceRt(
402 m_engineVoice->voiceId(),
403 [id = SourceId{ sound->sourceId }, oldState = std::move(oldState),
404 state = std::move(state)](QResonanceAudioPlayer &player) mutable {
405 player.setSoundPlaybackData(id, std::move(state));
406 });
407}
408
409void QAudioEngineWithPlayer::setOutputMode(QAudioEngine::OutputMode mode)
410{
411 if (outputMode() == mode)
412 return;
413 QAudioEnginePrivate::setOutputMode(mode);
414 if (!m_engineVoice)
415 return;
416
417 m_playbackEngine->visitVoiceRt(m_engineVoice->voiceId(), [mode](QResonanceAudioPlayer &player) {
418 player.setOutputMode(mode);
419 });
420}
421
423{
424 for (auto [sound, key] : playbackStates)
425 sound->updateRoomEffects();
426}
427
428QT_END_NAMESPACE
void addSound(QAmbientSoundPrivate *sound) override
void setSoundPlaybackData(QAmbientSoundPrivate *, SharedPlaybackState) override
void setPaused(bool paused) override
void setOutputMode(QAudioEngine::OutputMode) override
void setOutputDevice(const QAudioDevice &device) override
QAudioDevice outputDevice() const override
void removeSound(QAmbientSoundPrivate *sound) override
QResonanceAudioPlayer(QAudioEngineWithPlayer *player, VoiceId id)
void setOutputMode(QAudioEngine::OutputMode mode)
bool isActive() noexcept QT_MM_NONBLOCKING override
void setSoundPlaybackData(SourceId, SharedPlaybackState)
VoicePlayResult play(QSpan< float >) noexcept QT_MM_NONBLOCKING override
QRtAudioEngineVoice overrides.