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