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.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
4#include "qaudioengine.h"
6
7#include <QtMultimedia/private/qmultimedia_ranges_p.h>
8#include <QtSpatialAudio/private/qaudioengine_threaded_p.h>
9#include <QtSpatialAudio/private/qaudioengine_withplayer_p.h>
10#include <QtSpatialAudio/private/qaudioroom_p.h>
11#include <QtCore/qspan.h>
12
13#include <QtMultimedia/qaudiosink.h>
14#include <QtMultimedia/qmediadevices.h>
15#include <QtMultimedia/private/qaudiosystem_p.h>
16#include <QtMultimedia/private/qplatformaudiodevices_p.h>
17#include <QtMultimedia/private/qplatformmediaintegration_p.h>
18
19#include <q20vector.h>
20
21#include <resonance_audio.h>
22
23QT_BEGIN_NAMESPACE
24
25QAudioEnginePrivate::QAudioEnginePrivate(int sampleRate)
26 : m_sampleRate(sampleRate),
27 resonanceAudio{
28 std::make_unique<vraudio::ResonanceAudio>(2, qToUnderlying(framesPerBuffer), sampleRate),
29 }
30{
31 resonanceAudio->api->SetStereoSpeakerMode(outputMode() != QAudioEngine::Headphone);
32 resonanceAudio->api->SetMasterVolume(masterVolume());
33}
34
36
38{
39 if (scale == m_distanceScale)
40 return;
41 m_distanceScale = scale;
42 Q_Q(QAudioEngine);
43 emit q->distanceScaleChanged();
44}
45
47{
48 return m_distanceScale;
49}
50
52{
53 if (m_masterVolume == volume)
54 return;
55 m_masterVolume = volume;
56 resonanceAudio->api->SetMasterVolume(volume);
57 Q_Q(QAudioEngine);
58 emit q->masterVolumeChanged();
59}
60
62{
63 return m_masterVolume;
64}
65
66void QAudioEnginePrivate::setListenerPosition(std::optional<QVector3D> pos)
67{
68 if (pos == m_position)
69 return;
70
71 m_position = pos;
72
73 QVector3D posValue = pos.value_or(QVector3D{});
74 resonanceAudio->api->SetHeadPosition(posValue.x(), posValue.y(), posValue.z());
75
77}
78
79void QAudioEnginePrivate::setListenerRotation(const QQuaternion &rotation)
80{
81 resonanceAudio->api->SetHeadRotation(rotation.x(), rotation.y(), rotation.z(),
82 rotation.scalar());
83}
84
86{
87 if (resonanceAudio->roomEffectsEnabled == enabled)
88 return;
89 resonanceAudio->roomEffectsEnabled = enabled;
90}
91
93{
94 return resonanceAudio->roomEffectsEnabled;
95}
96
97void QAudioEnginePrivate::setOutputMode(QAudioEngine::OutputMode mode)
98{
99 if (m_outputMode == mode)
100 return;
101 m_outputMode = mode;
102 resonanceAudio->api->SetStereoSpeakerMode(mode != QAudioEngine::Headphone);
103
104 Q_Q(QAudioEngine);
105 emit q->outputModeChanged();
106}
107
108QAudioEngine::OutputMode QAudioEnginePrivate::outputMode() const
109{
110 return m_outputMode;
111}
112
113void QAudioEnginePrivate::addRoom(QAudioRoom *room)
114{
115 Q_ASSERT(!QtMultimediaPrivate::ranges::contains(rooms, room));
116 rooms.push_back(room);
117}
118
119void QAudioEnginePrivate::removeRoom(QAudioRoom *room)
120{
121 Q_ASSERT(QtMultimediaPrivate::ranges::contains(rooms, room));
122 q20::erase(rooms, room);
123}
124
126{
127 return m_currentRoom;
128}
129
131{
133 return;
134
135 bool roomDirty = false;
136 for (const auto &room : rooms) {
137 auto *rd = QAudioRoomPrivate::get(room);
138 if (rd->dirty) {
139 roomDirty = true;
140 rd->update();
141 }
142 }
143
144 auto inferredRoom = findSmallestRoomForListener(rooms);
145 if (inferredRoom.room != m_currentRoom)
146 roomDirty = true;
147 const bool previousRoom = m_currentRoom;
148 m_currentRoom = inferredRoom.room;
149
150 if (!roomDirty)
151 return;
152
153 // apply room to engine
154 if (!m_currentRoom) {
155 resonanceAudio->api->EnableRoomEffects(false);
156 return;
157 }
158 if (!previousRoom)
159 resonanceAudio->api->EnableRoomEffects(true);
160
161 QAudioRoomPrivate *rp = QAudioRoomPrivate::get(m_currentRoom);
162 resonanceAudio->api->SetReflectionProperties(rp->reflections);
163 resonanceAudio->api->SetReverbProperties(rp->reverb);
164
165 // update room effects for all sound sources
167}
168
170QAudioEnginePrivate::findSmallestRoomForListener(QSpan<QAudioRoom *> rooms) const
171{
172 const std::optional<QVector3D> listenerPos = listenerPosition();
173
174 if (!listenerPos)
176 nullptr,
177 0.f,
178 };
179
180 std::optional<float> roomVolume;
181 QAudioRoom *room = nullptr;
182
183 for (QAudioRoom *r : std::as_const(rooms)) {
184 QVector3D dim2 = r->dimensions() / 2.;
185 float vol = dim2.x() * dim2.y() * dim2.z();
186 if (roomVolume && vol > roomVolume)
187 continue;
188 QVector3D dist = r->position() - *listenerPos;
189 // transform into room coordinates
190 dist = r->rotation().rotatedVector(dist);
191 if (qAbs(dist.x()) <= dim2.x() && qAbs(dist.y()) <= dim2.y()
192 && qAbs(dist.z()) <= dim2.z()) {
193 room = r;
194 roomVolume = vol;
195 }
196 }
197
199 room,
200 roomVolume.value_or(0.f),
201 };
202}
203
204namespace {
205
206QAudioEnginePrivate *makeAudioEnginePrivate(int sampleRate)
207{
208 bool hasCallbackApi = QPlatformMediaIntegration::instance()->audioDevices()->hasCallbackApi();
209 if (hasCallbackApi)
210 return new QAudioEngineWithPlayer(sampleRate);
211 else
212 return new QAudioEngineThreaded(sampleRate);
213}
214
215} // namespace
216
217/*!
218 \class QAudioEngine
219 \inmodule QtSpatialAudio
220 \ingroup spatialaudio
221 \ingroup multimedia_audio
222
223 \brief QAudioEngine manages a three dimensional sound field.
224
225 You can use an instance of QAudioEngine to manage a sound field in
226 three dimensions. A sound field is defined by several QSpatialSound
227 objects that define a sound at a specified location in 3D space. You can also
228 add stereo overlays using QAmbientSound.
229
230 You can use QAudioListener to define the position of the person listening
231 to the sound field relative to the sound sources. Sound sources will be less audible
232 if the listener is further away from source. They will also get mapped to the corresponding
233 loudspeakers depending on the direction between listener and source.
234
235 QAudioEngine offers two output modes. The first mode renders the sound field to a set of
236 speakers, either a stereo speaker pair or a surround configuration. The second mode provides
237 an immersive 3D sound experience when using headphones.
238
239 Perception of sound localization is driven mainly by two factors. The first factor is timing
240 differences of the sound waves between left and right ear. The second factor comes from various
241 ways how sounds coming from different direcations create different types of reflections from our
242 ears and heads. See \l{https://en.wikipedia.org/wiki/Sound_localization} for more details.
243
244 The spatial audio engine emulates those timing differences and reflections through
245 Head related transfer functions (HRTF, see
246 \l{https://en.wikipedia.org/wiki/Head-related_transfer_function}). The functions used emulates those
247 effects for an average persons ears and head. It provides a good and immersive 3D sound localization
248 experience for most persons when using headphones.
249
250 The engine is rather versatile allowing you to define room properties and reverb settings to emulate
251 different types of rooms.
252
253 Sound sources can also be occluded dampening the sound coming from those sources.
254
255 The audio engine uses a coordinate system that is in centimeters by default. The axes are aligned with the
256 typical coordinate system used in 3D. Positive x points to the right, positive y points up and positive z points
257 backwards.
258
259*/
260
261/*!
262 \fn QAudioEngine::QAudioEngine()
263 \fn QAudioEngine::QAudioEngine(QObject *parent)
264 \fn QAudioEngine::QAudioEngine(int sampleRate, QObject *parent = nullptr)
265
266 Constructs a spatial audio engine with \a parent, if any.
267
268 The engine will operate with a sample rate given by \a sampleRate. The
269 default sample rate, if none is provided, is 44100 (44.1kHz).
270
271 Sound content that is not provided at that sample rate will automatically
272 get resampled to \a sampleRate when being processed by the engine. The
273 default sample rate is fine in most cases, but you can define a different
274 rate if most of your sound files are sampled with a different rate, and
275 avoid some CPU overhead for resampling.
276 */
277QAudioEngine::QAudioEngine(int sampleRate, QObject *parent)
278 : QObject(*makeAudioEnginePrivate(sampleRate), parent)
279{
280}
281
282/*!
283 Destroys the spatial audio engine.
284 */
285QAudioEngine::~QAudioEngine()
286{
287 stop();
288}
289
290/*! \enum QAudioEngine::OutputMode
291 \value Surround Map the sounds to the loudspeaker configuration of the output device.
292 This is normally a stereo or surround speaker setup.
293 \note OutputMode::Surround will disable playback of QAmbientSound
294 \value Stereo Map the sounds to the stereo loudspeaker configuration of the output device.
295 This will ignore any additional speakers and only use the left and right channels
296 to create a stero rendering of the sound field.
297 \value Headphone Use Headphone spatialization to create a 3D audio effect when listening
298 to the sound field through headphones
299*/
300
301/*!
302 \property QAudioEngine::outputMode
303
304 Sets or retrieves the current output mode of the engine.
305
306 \sa QAudioEngine::OutputMode
307 */
308void QAudioEngine::setOutputMode(OutputMode mode)
309{
310 Q_D(QAudioEngine);
311 d->setOutputMode(mode);
312}
313
314QAudioEngine::OutputMode QAudioEngine::outputMode() const
315{
316 Q_D(const QAudioEngine);
317 return d->outputMode();
318}
319
320/*!
321 Returns the sample rate the engine has been configured with.
322 */
323int QAudioEngine::sampleRate() const
324{
325 Q_D(const QAudioEngine);
326 return d->sampleRate();
327}
328
329/*!
330 \property QAudioEngine::outputDevice
331
332 Sets or returns the device that is being used for playing the sound field.
333 */
334void QAudioEngine::setOutputDevice(const QAudioDevice &device)
335{
336 Q_D(QAudioEngine);
337 d->setOutputDevice(device);
338}
339
340QAudioDevice QAudioEngine::outputDevice() const
341{
342 Q_D(const QAudioEngine);
343 return d->outputDevice();
344}
345
346/*!
347 \property QAudioEngine::masterVolume
348
349 Sets or returns volume being used to render the sound field.
350 */
351void QAudioEngine::setMasterVolume(float volume)
352{
353 Q_D(QAudioEngine);
354 return d->setMasterVolume(volume);
355}
356
357float QAudioEngine::masterVolume() const
358{
359 Q_D(const QAudioEngine);
360 return d->masterVolume();
361}
362
363/*!
364 Starts the engine.
365 */
366void QAudioEngine::start()
367{
368 Q_D(QAudioEngine);
369 d->start();
370}
371
372/*!
373 Stops the engine.
374 */
375void QAudioEngine::stop()
376{
377 Q_D(QAudioEngine);
378 d->stop();
379}
380
381/*!
382 \property QAudioEngine::paused
383
384 Pauses the spatial audio engine.
385 */
386void QAudioEngine::setPaused(bool paused)
387{
388 Q_D(QAudioEngine);
389 d->setPaused(paused);
390}
391
392bool QAudioEngine::paused() const
393{
394 Q_D(const QAudioEngine);
395 return d->isPaused();
396}
397
398/*!
399 Enables room effects such as echos and reverb.
400
401 Enables room effects if \a enabled is true.
402 Room effects will only apply if you create one or more \l QAudioRoom objects
403 and the listener is inside at least one of the rooms. If the listener is inside
404 multiple rooms, the room with the smallest volume will be used.
405 */
406void QAudioEngine::setRoomEffectsEnabled(bool enabled)
407{
408 Q_D(QAudioEngine);
409 d->setRoomEffectsEnabled(enabled);
410}
411
412/*!
413 Returns true if room effects are enabled.
414 */
415bool QAudioEngine::roomEffectsEnabled() const
416{
417 Q_D(const QAudioEngine);
418 return d->roomEffectsEnabled();
419}
420
421/*!
422 \property QAudioEngine::distanceScale
423
424 Defines the scale of the coordinate system being used by the spatial audio engine.
425 By default, all units are in centimeters, in line with the default units being
426 used by Qt Quick 3D.
427
428 Set the distance scale to QAudioEngine::DistanceScaleMeter to get units in meters.
429*/
430void QAudioEngine::setDistanceScale(float scale)
431{
432 Q_D(QAudioEngine);
433 // multiply with 100, to get the conversion to meters that resonance audio uses
434 scale /= 100.f;
435 if (scale <= 0.0f) {
436 qWarning() << "QAudioEngine: Invalid distance scale.";
437 return;
438 }
439 d->setDistanceScale(scale);
440}
441
442float QAudioEngine::distanceScale() const
443{
444 Q_D(const QAudioEngine);
445 return d->distanceScale() * 100.f;
446}
447
448/*!
449 \fn void QAudioEngine::pause()
450
451 Pauses playback.
452*/
453/*!
454 \fn void QAudioEngine::resume()
455
456 Resumes playback.
457*/
458/*!
459 \variable QAudioEngine::DistanceScaleCentimeter
460 \internal
461*/
462/*!
463 \variable QAudioEngine::DistanceScaleMeter
464 \internal
465*/
466
467QT_END_NAMESPACE
468
469#include "moc_qaudioengine.cpp"
void removeRoom(QAudioRoom *)
void setMasterVolume(float)
virtual void updateRoomEffects()=0
void addRoom(QAudioRoom *)
bool roomEffectsEnabled() const
float distanceScale() const
QAudioRoom * currentRoom() const
virtual void setOutputMode(QAudioEngine::OutputMode)
SmallestRoomForListenerResult findSmallestRoomForListener(QSpan< QAudioRoom * > rooms) const
void setListenerPosition(std::optional< QVector3D >)
float masterVolume() const
void setDistanceScale(float scale)
void setListenerRotation(const QQuaternion &)
~QAudioEnginePrivate() override
void setRoomEffectsEnabled(bool)