Qt 6.x
The Qt SDK
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 <qspatialsound_p.h>
5#include <qambientsound.h>
6#include <qaudioroom_p.h>
7#include <qaudiolistener.h>
8#include <resonance_audio.h>
10#include <qaudiodecoder.h>
11#include <qmediadevices.h>
12#include <qiodevice.h>
13#include <qaudiosink.h>
14#include <qdebug.h>
15#include <qelapsedtimer.h>
16
17#include <QFile>
18
20
21// We'd like to have short buffer times, so the sound adjusts itself to changes
22// quickly, but times below 100ms seem to give stuttering on macOS.
23// It might be possible to set this value lower on other OSes.
24const int bufferTimeMs = 100;
25
27{
29public:
31 : d(d)
32 {
34 }
36
37 qint64 readData(char *data, qint64 len) override;
38
39 qint64 writeData(const char *, qint64) override;
40
41 qint64 size() const override { return 0; }
42 qint64 bytesAvailable() const override {
43 return std::numeric_limits<qint64>::max();
44 }
45 bool isSequential() const override {
46 return true;
47 }
48 bool atEnd() const override {
49 return false;
50 }
51 qint64 pos() const override {
52 return m_pos;
53 }
54
56 QMutexLocker l(&d->mutex);
57 Q_ASSERT(!sink);
59 format.setChannelConfig(d->outputMode == QAudioEngine::Surround ?
61 format.setSampleRate(d->sampleRate);
62 format.setSampleFormat(QAudioFormat::Int16);
64 sink.reset(new QAudioSink(d->device, format));
65 sink->setBufferSize(d->sampleRate*bufferTimeMs/1000*sizeof(qint16)*format.channelCount());
66 sink->start(this);
67 }
68
70 sink->stop();
71 sink.reset();
72 d->ambisonicDecoder.reset();
73 }
74
76 stopOutput();
78 }
79
80 void setPaused(bool paused) {
81 if (paused)
82 sink->suspend();
83 else
84 sink->resume();
85 }
86
87private:
88 qint64 m_pos = 0;
89 QAudioEnginePrivate *d = nullptr;
90 std::unique_ptr<QAudioSink> sink;
91};
92
93
95{
96}
97
99{
100 return 0;
101}
102
104{
105 if (d->paused.loadRelaxed())
106 return 0;
107
108 d->updateRooms();
109
110 int nChannels = d->ambisonicDecoder ? d->ambisonicDecoder->nOutputChannels() : 2;
111 if (len < nChannels*int(sizeof(float))*QAudioEnginePrivate::bufferSize)
112 return 0;
113
114 short *fd = (short *)data;
115 qint64 frames = len / nChannels / sizeof(short);
116 bool ok = true;
117 while (frames >= qint64(QAudioEnginePrivate::bufferSize)) {
118 // Fill input buffers
119 for (auto *source : std::as_const(d->sources)) {
122 sp->getBuffer(buf, QAudioEnginePrivate::bufferSize, 1);
123 d->resonanceAudio->api->SetInterleavedBuffer(sp->sourceId, buf, 1, QAudioEnginePrivate::bufferSize);
124 }
125 for (auto *source : std::as_const(d->stereoSources)) {
128 sp->getBuffer(buf, QAudioEnginePrivate::bufferSize, 2);
129 d->resonanceAudio->api->SetInterleavedBuffer(sp->sourceId, buf, 2, QAudioEnginePrivate::bufferSize);
130 }
131
133 const float *channels[QAmbisonicDecoder::maxAmbisonicChannels];
134 const float *reverbBuffers[2];
135 int nSamples = d->resonanceAudio->getAmbisonicOutput(channels, reverbBuffers, d->ambisonicDecoder->nInputChannels());
136 Q_ASSERT(d->ambisonicDecoder->nOutputChannels() <= 8);
137 d->ambisonicDecoder->processBufferWithReverb(channels, reverbBuffers, fd, nSamples);
138 } else {
139 ok = d->resonanceAudio->api->FillInterleavedOutputBuffer(2, QAudioEnginePrivate::bufferSize, fd);
140 if (!ok) {
141 qWarning() << " Reading failed!";
142 break;
143 }
144 }
147 }
148 const int bytesProcessed = ((char *)fd - data);
149 m_pos += bytesProcessed;
150 return bytesProcessed;
151}
152
153
155{
158}
159
161{
162 delete resonanceAudio;
163}
164
166{
168
169 sd->sourceId = resonanceAudio->api->CreateSoundObjectSource(vraudio::kBinauralHighQuality);
170 sources.append(sound);
171}
172
174{
176
177 resonanceAudio->api->DestroySource(sd->sourceId);
178 sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
179 sources.removeOne(sound);
180}
181
183{
185
186 sd->sourceId = resonanceAudio->api->CreateStereoSource(2);
187 stereoSources.append(sound);
188}
189
191{
193
194 resonanceAudio->api->DestroySource(sd->sourceId);
195 sd->sourceId = vraudio::ResonanceAudioApi::kInvalidSourceId;
197}
198
200{
201 rooms.append(room);
202}
203
205{
206 rooms.removeOne(room);
207}
208
210{
212 return;
213
214 bool needUpdate = listenerPositionDirty;
215 listenerPositionDirty = false;
216
217 bool roomDirty = false;
218 for (const auto &room : rooms) {
219 auto *rd = QAudioRoomPrivate::get(room);
220 if (rd->dirty) {
221 roomDirty = true;
222 rd->update();
223 needUpdate = true;
224 }
225 }
226
227 if (!needUpdate)
228 return;
229
230 QVector3D listenerPos = listenerPosition();
231 float roomVolume = float(qInf());
232 QAudioRoom *room = nullptr;
233 // Find the smallest room that contains the listener and apply its room effects
234 for (auto *r : std::as_const(rooms)) {
235 QVector3D dim2 = r->dimensions()/2.;
236 float vol = dim2.x()*dim2.y()*dim2.z();
237 if (vol > roomVolume)
238 continue;
239 QVector3D dist = r->position() - listenerPos;
240 // transform into room coordinates
241 dist = r->rotation().rotatedVector(dist);
242 if (qAbs(dist.x()) <= dim2.x() &&
243 qAbs(dist.y()) <= dim2.y() &&
244 qAbs(dist.z()) <= dim2.z()) {
245 room = r;
246 roomVolume = vol;
247 }
248 }
249 if (room != currentRoom)
250 roomDirty = true;
251 const bool previousRoom = currentRoom;
252 currentRoom = room;
253
254 if (!roomDirty)
255 return;
256
257 // apply room to engine
258 if (!currentRoom) {
259 resonanceAudio->api->EnableRoomEffects(false);
260 return;
261 }
262 if (!previousRoom)
263 resonanceAudio->api->EnableRoomEffects(true);
264
266 resonanceAudio->api->SetReflectionProperties(rp->reflections);
267 resonanceAudio->api->SetReverbProperties(rp->reverb);
268
269 // update room effects for all sound sources
270 for (auto *s : std::as_const(sources)) {
272 sp->updateRoomEffects();
273 }
274}
275
277{
278 return listener ? listener->position() : QVector3D();
279}
280
281
343 : QObject(parent)
345{
348}
349
354{
355 stop();
356 delete d;
357}
358
377{
378 if (d->outputMode == mode)
379 return;
380 d->outputMode = mode;
381 if (d->resonanceAudio->api)
382 d->resonanceAudio->api->SetStereoSpeakerMode(mode != Headphone);
383
385
387}
388
390{
391 return d->outputMode;
392}
393
398{
399 return d->sampleRate;
400}
401
408{
409 if (d->device == device)
410 return;
411 if (d->resonanceAudio->api) {
412 qWarning() << "Changing device on a running engine not implemented";
413 return;
414 }
415 d->device = device;
417}
418
420{
421 return d->device;
422}
423
430{
431 if (d->masterVolume == volume)
432 return;
433 d->masterVolume = volume;
434 d->resonanceAudio->api->SetMasterVolume(volume);
436}
437
439{
440 return d->masterVolume;
441}
442
447{
448 if (d->outputStream)
449 // already started
450 return;
451
452 d->resonanceAudio->api->SetStereoSpeakerMode(d->outputMode != Headphone);
453 d->resonanceAudio->api->SetMasterVolume(d->masterVolume);
454
455 d->outputStream.reset(new QAudioOutputStream(d));
456 d->outputStream->moveToThread(&d->audioThread);
457 d->audioThread.start();
458
459 QMetaObject::invokeMethod(d->outputStream.get(), "startOutput");
460}
461
466{
468 d->outputStream.reset();
469 d->audioThread.exit(0);
470 d->audioThread.wait();
471 delete d->resonanceAudio->api;
472 d->resonanceAudio->api = nullptr;
473}
474
480void QAudioEngine::setPaused(bool paused)
481{
482 bool old = d->paused.fetchAndStoreRelaxed(paused);
483 if (old != paused) {
484 if (d->outputStream)
485 d->outputStream->setPaused(paused);
487 }
488}
489
491{
492 return d->paused.loadRelaxed();
493}
494
504{
505 if (d->roomEffectsEnabled == enabled)
506 return;
509}
510
515{
516 return d->roomEffectsEnabled;
517}
518
529{
530 // multiply with 100, to get the conversion to meters that resonance audio uses
531 scale /= 100.f;
532 if (scale <= 0.0f) {
533 qWarning() << "QAudioEngine: Invalid distance scale.";
534 return;
535 }
536 if (scale == d->distanceScale)
537 return;
538 d->distanceScale = scale;
540}
541
543{
544 return d->distanceScale*100.f;
545}
546
547
549{
550 decoder.reset(new QAudioDecoder);
551 buffers.clear();
552 currentBuffer = 0;
553 sourceDeviceFile.reset(nullptr);
554 bufPos = 0;
555 m_playing = false;
556 m_loading = true;
559 f.setSampleFormat(QAudioFormat::Float);
560 f.setSampleRate(ep->sampleRate);
562 decoder->setAudioFormat(f);
563 if (url.scheme().compare(u"qrc", Qt::CaseInsensitive) == 0) {
564 auto qrcFile = std::make_unique<QFile>(u':' + url.path());
565 if (!qrcFile->open(QFile::ReadOnly))
566 return;
567 sourceDeviceFile = std::move(qrcFile);
568 decoder->setSourceDevice(sourceDeviceFile.get());
569 } else {
570 decoder->setSource(url);
571 }
572 connect(decoder.get(), &QAudioDecoder::bufferReady, this, &QAmbientSoundPrivate::bufferReady);
573 connect(decoder.get(), &QAudioDecoder::finished, this, &QAmbientSoundPrivate::finished);
574 decoder->start();
575}
576
577void QAmbientSoundPrivate::getBuffer(float *buf, int nframes, int channels)
578{
579 Q_ASSERT(channels == nchannels);
581 if (!m_playing || currentBuffer >= buffers.size()) {
582 memset(buf, 0, channels * nframes * sizeof(float));
583 } else {
584 int frames = nframes;
585 float *ff = buf;
586 while (frames) {
587 if (currentBuffer < buffers.size()) {
588 const QAudioBuffer &b = buffers.at(currentBuffer);
589// qDebug() << s << b.format().sampleRate() << b.format().channelCount() << b.format().sampleFormat();
590 auto *f = b.constData<float>() + bufPos*nchannels;
591 int toCopy = qMin(b.frameCount() - bufPos, frames);
592 memcpy(ff, f, toCopy*sizeof(float)*nchannels);
593 ff += toCopy*nchannels;
594 frames -= toCopy;
595 bufPos += toCopy;
596 Q_ASSERT(bufPos <= b.frameCount());
597 if (bufPos == b.frameCount()) {
599 bufPos = 0;
600 }
601 } else {
602 // no more data available
603 if (m_loading)
604 qDebug() << "underrun" << frames << "frames when loading" << url;
605 memset(ff, 0, frames * channels * sizeof(float));
606 ff += frames * channels;
607 frames = 0;
608 }
609 if (!m_loading) {
610 if (currentBuffer == buffers.size()) {
611 currentBuffer = 0;
613 }
614 if (m_loops > 0 && m_currentLoop >= m_loops) {
615 m_playing = false;
616 m_currentLoop = 0;
617 }
618 }
619 }
620 Q_ASSERT(ff - buf == channels*nframes);
621 }
622}
623
624void QAmbientSoundPrivate::bufferReady()
625{
627 auto b = decoder->read();
628// qDebug() << "read buffer" << b.format() << b.startTime() << b.duration();
629 buffers.append(b);
630 if (m_autoPlay)
631 m_playing = true;
632}
633
634void QAmbientSoundPrivate::finished()
635{
636 m_loading = false;
637}
638
659
660#include "moc_qaudioengine.cpp"
661#include "qaudioengine.moc"
IOBluetoothDevice * device
std::unique_ptr< QAudioDecoder > decoder
QAtomicInteger< bool > m_playing
QAudioEngine * engine
static QAmbientSoundPrivate * get(T *soundSource)
QAtomicInteger< bool > m_autoPlay
void getBuffer(float *buf, int frames, int channels)
std::unique_ptr< QFile > sourceDeviceFile
\inmodule QtSpatialAudio
static constexpr int maxAmbisonicChannels
\inmodule QtMultimedia
const T * constData() const
The QAudioDecoder class implements decoding audio.
void bufferReady()
Signals that a new decoded audio buffer is available to be read.
void finished()
Signals that the decoding has finished successfully.
The QAudioDevice class provides an information about audio devices and their functionality.
QAudioFormat::ChannelConfig channelConfiguration() const
Returns the channel configuration of the 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)
std::unique_ptr< QAmbisonicDecoder > ambisonicDecoder
void addSpatialSound(QSpatialSound *sound)
QAudioEngine::OutputMode outputMode
static QAudioEnginePrivate * get(QAudioEngine *engine)
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.
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 removeOne(const AT &t)
Definition qlist.h:581
void append(parameter_type t)
Definition qlist.h:441
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
Definition qmutex.h:317
\inmodule QtCore
Definition qobject.h:90
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
static QSpatialSoundPrivate * get(QSpatialSound *soundSource)
\inmodule QtSpatialAudio
int compare(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const noexcept
Definition qstring.cpp:6498
void start(Priority=InheritPriority)
Definition qthread.cpp:923
@ TimeCriticalPriority
Definition qthread.h:50
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:950
void setPriority(Priority priority)
void exit(int retcode=0)
Definition qthread.cpp:940
QString scheme() const
Returns the scheme of the URL.
Definition qurl.cpp:1983
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2465
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
constexpr float z() const noexcept
Returns the z coordinate of this point.
Definition qvectornd.h:672
ResonanceAudioApi * api
int getAmbisonicOutput(const float *buffers[], const float *reverb[], int nChannels)
Combined button and popup list for selecting options.
@ CaseInsensitive
@ BlockingQueuedConnection
QT_BEGIN_NAMESPACE const int bufferTimeMs
#define qDebug
[1]
Definition qlogging.h:160
#define qWarning
Definition qlogging.h:162
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
Q_CORE_EXPORT Q_DECL_CONST_FUNCTION double qInf()
GLboolean GLboolean GLboolean b
GLuint const GLuint * buffers
GLenum mode
GLboolean r
[2]
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLfloat GLfloat f
GLenum GLuint GLenum GLsizei const GLchar * buf
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLuint64 GLenum GLint fd
GLsizei GLenum * sources
GLint GLsizei GLsizei GLenum format
GLsizei GLsizei GLchar * source
GLenum GLsizei len
GLsizei GLenum GLboolean sink
GLdouble s
[6]
Definition qopenglext.h:235
GLenum GLenum GLenum GLenum GLenum scale
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define sp
#define Q_OBJECT
#define Q_INVOKABLE
#define emit
short qint16
Definition qtypes.h:42
long long qint64
Definition qtypes.h:55
#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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent