Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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#include <qaudioengine_p.h>
4#include <qambientsound_p.h>
5#include <qspatialsound_p.h>
6#include <qambientsound.h>
7#include <qaudioroom_p.h>
8#include <qaudiolistener.h>
9#include <resonance_audio.h>
10#include <qambisonicdecoder_p.h>
11#include <qaudiodecoder.h>
12#include <qmediadevices.h>
13#include <qiodevice.h>
14#include <qaudiosink.h>
15#include <qdebug.h>
16#include <qelapsedtimer.h>
17
18#include <QFile>
19
21
22// We'd like to have short buffer times, so the sound adjusts itself to changes
23// quickly, but times below 100ms seem to give stuttering on macOS.
24// It might be possible to set this value lower on other OSes.
25const int bufferTimeMs = 100;
26
27// This class lives in the audioThread, but pulls data from QAudioEnginePrivate
28// which lives in the mainThread.
30{
32public:
39
40 qint64 readData(char *data, qint64 len) override;
41
42 qint64 writeData(const char *, qint64) override;
43
44 qint64 size() const override { return 0; }
45 qint64 bytesAvailable() const override {
46 return std::numeric_limits<qint64>::max();
47 }
48 bool isSequential() const override {
49 return true;
50 }
51 bool atEnd() const override {
52 return false;
53 }
54 qint64 pos() const override {
55 return m_pos;
56 }
57
59 d->mutex.lock();
60 Q_ASSERT(!sink);
65 format.setChannelConfig(channelConfig);
66 else
67 format.setChannelCount(d->device.preferredFormat().channelCount());
68 format.setSampleRate(d->sampleRate);
69 format.setSampleFormat(QAudioFormat::Int16);
70 ambisonicDecoder.reset(new QAmbisonicDecoder(QAmbisonicDecoder::HighQuality, format));
71 sink.reset(new QAudioSink(d->device, format));
72 const qsizetype bufferSize = format.bytesForDuration(bufferTimeMs * 1000);
73 sink->setBufferSize(bufferSize);
74 d->mutex.unlock();
75 // It is important to unlock the mutex before starting the sink, as the sink will
76 // call readData() in the audio thread, which will try to lock the mutex (again)
77 sink->start(this);
78 }
79
81 sink->stop();
82 sink.reset();
83 ambisonicDecoder.reset();
84 }
85
90
91 void setPaused(bool paused) {
92 if (paused)
93 sink->suspend();
94 else
95 sink->resume();
96 }
97
98private:
99 qint64 m_pos = 0;
100 QAudioEnginePrivate *d = nullptr;
101 std::unique_ptr<QAudioSink> sink;
102 std::unique_ptr<QAmbisonicDecoder> ambisonicDecoder;
103};
104
105
109
111{
112 return 0;
113}
114
116{
117 if (d->paused.loadRelaxed())
118 return 0;
119
120 QMutexLocker l(&d->mutex);
121 d->updateRooms();
122
123 int nChannels = ambisonicDecoder ? ambisonicDecoder->nOutputChannels() : 2;
124 if (len < nChannels*int(sizeof(float))*QAudioEnginePrivate::bufferSize)
125 return 0;
126
127 short *fd = (short *)data;
128 qint64 frames = len / nChannels / sizeof(short);
129 bool ok = true;
130 while (frames >= qint64(QAudioEnginePrivate::bufferSize)) {
131 // Fill input buffers
132 for (auto *source : std::as_const(d->sources)) {
134 if (!sp)
135 continue;
137 sp->getBuffer(buf, QAudioEnginePrivate::bufferSize, 1);
138 d->resonanceAudio->api->SetInterleavedBuffer(sp->sourceId, buf, 1, QAudioEnginePrivate::bufferSize);
139 }
140 for (auto *source : std::as_const(d->stereoSources)) {
142 if (!sp)
143 continue;
145 sp->getBuffer(buf, QAudioEnginePrivate::bufferSize, 2);
146 d->resonanceAudio->api->SetInterleavedBuffer(sp->sourceId, buf, 2, QAudioEnginePrivate::bufferSize);
147 }
148
149 if (ambisonicDecoder && d->outputMode == QAudioEngine::Surround) {
150 const float *channels[QAmbisonicDecoder::maxAmbisonicChannels];
151 const float *reverbBuffers[2];
152 int nSamples = d->resonanceAudio->getAmbisonicOutput(channels, reverbBuffers, ambisonicDecoder->nInputChannels());
153 Q_ASSERT(ambisonicDecoder->nOutputChannels() <= 8);
154 ambisonicDecoder->processBufferWithReverb(channels, reverbBuffers, fd, nSamples);
155 } else {
156 ok = d->resonanceAudio->api->FillInterleavedOutputBuffer(2, QAudioEnginePrivate::bufferSize, fd);
157 if (!ok) {
158 // If we get here, it means that resonanceAudio did not actually fill the buffer.
159 // Sometimes this is expected, for example if resonanceAudio does not have any sources.
160 // In this case we just fill the buffer with silence.
161 if (d->sources.isEmpty() && d->stereoSources.isEmpty()) {
162 memset(fd, 0, nChannels * QAudioEnginePrivate::bufferSize * sizeof(short));
163 } else {
164 // If we get here, it means that something unexpected happened, so bail.
165 qWarning() << " Reading failed!";
166 break;
167 }
168 }
169 }
172 }
173 const int bytesProcessed = ((char *)fd - data);
174 m_pos += bytesProcessed;
175 return bytesProcessed;
176}
177
178
183
188
190{
193
194 sd->sourceId = resonanceAudio->api->CreateSoundObjectSource(vraudio::kBinauralHighQuality);
195 sources.append(sound);
196}
197
199{
202
203 resonanceAudio->api->DestroySource(sd->sourceId);
204 sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
205 sources.removeOne(sound);
206}
207
209{
212
213 sd->sourceId = resonanceAudio->api->CreateStereoSource(2);
214 stereoSources.append(sound);
215}
216
218{
221
222 resonanceAudio->api->DestroySource(sd->sourceId);
223 sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
225}
226
228{
230 rooms.append(room);
231}
232
238
239// This method is called from the audio thread
241{
243 return;
244
245 bool needUpdate = listenerPositionDirty;
246 listenerPositionDirty = false;
247
248 bool roomDirty = false;
249 for (const auto &room : rooms) {
250 auto *rd = QAudioRoomPrivate::get(room);
251 if (rd->dirty) {
252 roomDirty = true;
253 rd->update();
254 needUpdate = true;
255 }
256 }
257
258 if (!needUpdate)
259 return;
260
261 QVector3D listenerPos = listenerPosition();
262 float roomVolume = float(qInf());
263 QAudioRoom *room = nullptr;
264 // Find the smallest room that contains the listener and apply its room effects
265 for (auto *r : std::as_const(rooms)) {
266 QVector3D dim2 = r->dimensions()/2.;
267 float vol = dim2.x()*dim2.y()*dim2.z();
268 if (vol > roomVolume)
269 continue;
270 QVector3D dist = r->position() - listenerPos;
271 // transform into room coordinates
272 dist = r->rotation().rotatedVector(dist);
273 if (qAbs(dist.x()) <= dim2.x() &&
274 qAbs(dist.y()) <= dim2.y() &&
275 qAbs(dist.z()) <= dim2.z()) {
276 room = r;
277 roomVolume = vol;
278 }
279 }
280 if (room != currentRoom)
281 roomDirty = true;
282 const bool previousRoom = currentRoom;
283 currentRoom = room;
284
285 if (!roomDirty)
286 return;
287
288 // apply room to engine
289 if (!currentRoom) {
290 resonanceAudio->api->EnableRoomEffects(false);
291 return;
292 }
293 if (!previousRoom)
294 resonanceAudio->api->EnableRoomEffects(true);
295
297 resonanceAudio->api->SetReflectionProperties(rp->reflections);
298 resonanceAudio->api->SetReverbProperties(rp->reverb);
299
300 // update room effects for all sound sources
301 for (auto *s : std::as_const(sources)) {
303 if (!sp)
304 continue;
305 sp->updateRoomEffects();
306 }
307}
308
313
314
382
387{
388 stop();
389 delete d;
390}
391
410{
411 if (d->outputMode == mode)
412 return;
413 d->outputMode = mode;
414 if (d->resonanceAudio->api)
415 d->resonanceAudio->api->SetStereoSpeakerMode(mode != Headphone);
416
418
420}
421
426
431{
432 return d->sampleRate;
433}
434
441{
442 if (d->device == device)
443 return;
444 if (d->resonanceAudio->api) {
445 qWarning() << "Changing device on a running engine not implemented";
446 return;
447 }
448 d->device = device;
450}
451
453{
454 return d->device;
455}
456
463{
464 if (d->masterVolume == volume)
465 return;
466 d->masterVolume = volume;
467 d->resonanceAudio->api->SetMasterVolume(volume);
469}
470
472{
473 return d->masterVolume;
474}
475
480{
481 if (d->outputStream)
482 // already started
483 return;
484
485 d->resonanceAudio->api->SetStereoSpeakerMode(d->outputMode != Headphone);
486 d->resonanceAudio->api->SetMasterVolume(d->masterVolume);
487
488 d->outputStream.reset(new QAudioOutputStream(d));
489 d->outputStream->moveToThread(&d->audioThread);
491
492 QMetaObject::invokeMethod(d->outputStream.get(), "startOutput");
493}
494
499{
501 d->outputStream.reset();
502 d->audioThread.exit(0);
503 d->audioThread.wait();
504 delete d->resonanceAudio->api;
505 d->resonanceAudio->api = nullptr;
506}
507
513void QAudioEngine::setPaused(bool paused)
514{
515 bool old = d->paused.fetchAndStoreRelaxed(paused);
516 if (old != paused) {
517 if (d->outputStream)
518 d->outputStream->setPaused(paused);
520 }
521}
522
524{
525 return d->paused.loadRelaxed();
526}
527
543
548{
549 return d->roomEffectsEnabled;
550}
551
562{
563 // multiply with 100, to get the conversion to meters that resonance audio uses
564 scale /= 100.f;
565 if (scale <= 0.0f) {
566 qWarning() << "QAudioEngine: Invalid distance scale.";
567 return;
568 }
569 if (scale == d->distanceScale)
570 return;
571 d->distanceScale = scale;
573}
574
576{
577 return d->distanceScale*100.f;
578}
579
600
601#include "moc_qaudioengine.cpp"
602#include "qaudioengine.moc"
IOBluetoothDevice * device
static QAmbientSoundPrivate * get(T *soundSource)
\inmodule QtSpatialAudio
static constexpr int maxAmbisonicChannels
The QAudioDevice class provides an information about audio devices and their functionality.
QAudioFormat::ChannelConfig channelConfiguration() const
Returns the channel configuration of the device.
QAudioFormat preferredFormat() const
Returns the default audio format settings for this device.
std::unique_ptr< QAudioOutputStream > outputStream
vraudio::ResonanceAudio * resonanceAudio
void addRoom(QAudioRoom *room)
void addStereoSound(QAmbientSound *sound)
QAtomicInteger< bool > paused
QAudioRoom * currentRoom
QVector3D listenerPosition() const
void removeRoom(QAudioRoom *room)
void addSpatialSound(QSpatialSound *sound)
QAudioEngine::OutputMode outputMode
QList< QAmbientSound * > stereoSources
QList< QSpatialSound * > sources
void removeStereoSound(QAmbientSound *sound)
void removeSpatialSound(QSpatialSound *sound)
QList< QAudioRoom * > rooms
QAudioListener * listener
static constexpr int bufferSize
OutputMode
\value Surround Map the sounds to the loudspeaker configuration of the output device.
void pausedChanged()
void outputDeviceChanged()
QAudioDevice outputDevice
Sets or returns the device that is being used for playing the sound field.
void stop()
Stops the engine.
void setDistanceScale(float scale)
void setOutputDevice(const QAudioDevice &device)
void setOutputMode(OutputMode mode)
float distanceScale
Defines the scale of the coordinate system being used by the spatial audio engine.
int sampleRate() const
Returns the sample rate the engine has been configured with.
bool paused
Pauses the spatial audio engine.
OutputMode outputMode
Sets or retrieves the current output mode of the engine.
void distanceScaleChanged()
void outputModeChanged()
void setPaused(bool paused)
~QAudioEngine()
Destroys the spatial audio engine.
float masterVolume
Sets or returns volume being used to render the sound field.
void setMasterVolume(float volume)
void setRoomEffectsEnabled(bool enabled)
Enables room effects such as echos and reverb.
void start()
Starts the engine.
bool roomEffectsEnabled() const
Returns true if room effects are enabled.
void masterVolumeChanged()
The QAudioFormat class stores audio stream parameter information.
constexpr int channelCount() const noexcept
Returns the current channel count value.
QVector3D position() const
Returns the current position of the listener.
void setPaused(bool paused)
QAudioOutputStream(QAudioEnginePrivate *d)
qint64 readData(char *data, qint64 len) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
Q_INVOKABLE void restartOutput()
bool atEnd() const override
Returns true if the current read and write position is at the end of the device (i....
Q_INVOKABLE void stopOutput()
qint64 pos() const override
For random-access devices, this function returns the position that data is written to or read from.
bool isSequential() const override
Returns true if this device is sequential; otherwise returns false.
qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
qint64 writeData(const char *, qint64) override
Writes up to maxSize bytes from data to the device.
Q_INVOKABLE void startOutput()
qint64 size() const override
For open random-access devices, this function returns the size of the device.
vraudio::ReflectionProperties reflections
static QAudioRoomPrivate * get(const QAudioRoom *r)
vraudio::ReverbProperties reverb
\inmodule QtSpatialAudio
Definition qaudioroom.h:17
The QAudioSink class provides an interface for sending audio data to an audio output device.
Definition qaudiosink.h:24
T fetchAndStoreRelaxed(T newValue) noexcept
T loadRelaxed() const noexcept
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
bool isEmpty() const noexcept
Definition qlist.h:401
bool removeOne(const AT &t)
Definition qlist.h:598
void append(parameter_type t)
Definition qlist.h:458
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
Definition qmutex.h:313
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
\inmodule QtCore
Definition qobject.h:103
static QSpatialSoundPrivate * get(QSpatialSound *soundSource)
\inmodule QtSpatialAudio
void start(Priority=InheritPriority)
Definition qthread.cpp:996
@ TimeCriticalPriority
Definition qthread.h:49
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
void exit(int retcode=0)
Definition qthread.cpp:1013
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
ResonanceAudioApi * api
int getAmbisonicOutput(const float *buffers[], const float *reverb[], int nChannels)
Combined button and popup list for selecting options.
@ BlockingQueuedConnection
QT_BEGIN_NAMESPACE const int bufferTimeMs
QAudioFormat::ChannelConfig channelConfig
#define qWarning
Definition qlogging.h:166
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION double qInf()
GLenum mode
GLboolean r
[2]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLenum GLuint GLenum GLsizei const GLchar * buf
GLuint64 GLenum GLint fd
GLsizei GLenum * sources
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLdouble s
[6]
Definition qopenglext.h:235
GLsizei GLenum GLboolean sink
GLenum GLsizei len
GLenum GLenum GLenum GLenum GLenum scale
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define sp
#define Q_OBJECT
#define Q_INVOKABLE
#define emit
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
#define enabled
QRandomGenerator64 rd
[10]
std::uniform_real_distribution dist(1, 2.5)
[2]
QByteArray readData()
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...