1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the plugins of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qaudioengine_openal_p.h"
41
42#include <QtCore/QMutex>
43#include <QtCore/QThread>
44#include <QtNetwork/QNetworkRequest>
45#include <QtNetwork/QNetworkReply>
46#include <QtNetwork/QNetworkAccessManager>
47
48#include "qsamplecache_p.h"
49
50#include "qdebug.h"
51
52#define DEBUG_AUDIOENGINE
53
54QT_USE_NAMESPACE
55
56QSoundBufferPrivateAL::QSoundBufferPrivateAL(QObject *parent)
57 : QSoundBuffer(parent)
58{
59}
60
61
62StaticSoundBufferAL::StaticSoundBufferAL(QObject *parent, const QUrl &url, QSampleCache *sampleLoader)
63 : QSoundBufferPrivateAL(parent),
64 m_ref(1),
65 m_url(url),
66 m_alBuffer(0),
67 m_state(Creating),
68 m_sample(0),
69 m_sampleLoader(sampleLoader)
70{
71#ifdef DEBUG_AUDIOENGINE
72 qDebug() << "creating new StaticSoundBufferOpenAL";
73#endif
74}
75
76StaticSoundBufferAL::~StaticSoundBufferAL()
77{
78 if (m_sample)
79 m_sample->release();
80
81 if (m_alBuffer != 0) {
82 alGetError(); // clear error
83 alDeleteBuffers(1, &m_alBuffer);
84 QAudioEnginePrivate::checkNoError("delete buffer");
85 }
86}
87
88QSoundBuffer::State StaticSoundBufferAL::state() const
89{
90 return m_state;
91}
92
93void StaticSoundBufferAL::load()
94{
95 if (m_state == Loading || m_state == Ready)
96 return;
97
98 m_state = Loading;
99 emit stateChanged(m_state);
100
101 m_sample = m_sampleLoader->requestSample(m_url);
102 connect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
103 connect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
104 switch (m_sample->state()) {
105 case QSample::Ready:
106 sampleReady();
107 break;
108 case QSample::Error:
109 decoderError();
110 break;
111 default:
112 break;
113 }
114}
115
116void StaticSoundBufferAL::bindToSource(ALuint alSource)
117{
118 Q_ASSERT(m_alBuffer != 0);
119 alSourcei(alSource, AL_BUFFER, m_alBuffer);
120}
121
122void StaticSoundBufferAL::unbindFromSource(ALuint alSource)
123{
124 alSourcei(alSource, AL_BUFFER, 0);
125}
126
127void StaticSoundBufferAL::sampleReady()
128{
129#ifdef DEBUG_AUDIOENGINE
130 qDebug() << "StaticSoundBufferOpenAL:sample[" << m_url << "] loaded";
131#endif
132
133 disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
134 disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
135
136 if (m_sample->data().size() > 1024 * 1024 * 4) {
137 qWarning() << "source [" << m_url << "] size too large!";
138 decoderError();
139 return;
140 }
141
142 if (m_sample->format().channelCount() > 2) {
143 qWarning() << "source [" << m_url << "] channel > 2!";
144 decoderError();
145 return;
146 }
147
148 ALenum alFormat = 0;
149 if (m_sample->format().sampleSize() == 8) {
150 alFormat = m_sample->format().channelCount() == 1 ? AL_FORMAT_MONO8 : AL_FORMAT_STEREO8;
151 } else if (m_sample->format().sampleSize() == 16) {
152 alFormat = m_sample->format().channelCount() == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
153 } else {
154 qWarning() << "source [" << m_url << "] invalid sample size:"
155 << m_sample->format().sampleSize() << "(should be 8 or 16)";
156 decoderError();
157 return;
158 }
159
160 alGenBuffers(1, &m_alBuffer);
161 if (!QAudioEnginePrivate::checkNoError("create buffer")) {
162 decoderError();
163 return;
164 }
165 alBufferData(m_alBuffer, alFormat, m_sample->data().data(),
166 m_sample->data().size(), m_sample->format().sampleRate());
167 if (!QAudioEnginePrivate::checkNoError("fill buffer")) {
168 decoderError();
169 return;
170 }
171
172 m_sample->release();
173 m_sample = 0;
174
175 m_state = Ready;
176 emit stateChanged(m_state);
177 emit ready();
178}
179
180void StaticSoundBufferAL::decoderError()
181{
182 qWarning() << "loading [" << m_url << "] failed";
183
184 disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
185 disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
186
187 m_sample->release();
188 m_sample = 0;
189
190 m_state = Error;
191 emit stateChanged(m_state);
192 emit error();
193}
194
195
196/////////////////////////////////////////////////////////////////
197QAudioEnginePrivate::QAudioEnginePrivate(QObject *parent)
198 : QObject(parent)
199{
200 m_updateTimer.setInterval(200);
201 connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateSoundSources()));
202
203 m_sampleLoader = new QSampleCache(this);
204 m_sampleLoader->setCapacity(0);
205 connect(m_sampleLoader, SIGNAL(isLoadingChanged()), this, SIGNAL(isLoadingChanged()));
206
207#ifdef DEBUG_AUDIOENGINE
208 qDebug() << "default openal device = " << alcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER);
209
210 const ALCchar* devNames = alcGetString(NULL, ALC_DEVICE_SPECIFIER);
211 int cc = 0;
212 qDebug() << "device list:";
213 while (true) {
214 qDebug() << " " << devNames + cc;
215 while (devNames[cc] != 0)
216 ++cc;
217 ++cc;
218 if (devNames[cc] == 0)
219 break;
220 }
221#endif
222
223 ALCdevice *device = alcOpenDevice(0);
224 if (!device) {
225 qWarning() << "Can not create openal device!";
226 return;
227 }
228
229 ALCcontext* context = alcCreateContext(device, 0);
230 if (!context) {
231 qWarning() << "Can not create openal context!";
232 return;
233 }
234 alcMakeContextCurrent(context);
235 alDistanceModel(AL_NONE);
236 alDopplerFactor(0);
237}
238
239QAudioEnginePrivate::~QAudioEnginePrivate()
240{
241#ifdef DEBUG_AUDIOENGINE
242 qDebug() << "QAudioEnginePrivate::dtor";
243#endif
244 const QObjectList children = this->children();
245 for (QObject *child : children) {
246 QSoundSourcePrivate* s = qobject_cast<QSoundSourcePrivate*>(child);
247 if (!s)
248 continue;
249 s->release();
250 }
251
252 for (QSoundBufferPrivateAL *buffer : qAsConst(m_staticBufferPool)) {
253 delete buffer;
254 }
255 m_staticBufferPool.clear();
256
257 delete m_sampleLoader;
258
259 ALCcontext* context = alcGetCurrentContext();
260 ALCdevice *device = alcGetContextsDevice(context);
261 alcDestroyContext(context);
262 alcCloseDevice(device);
263#ifdef DEBUG_AUDIOENGINE
264 qDebug() << "QAudioEnginePrivate::dtor: all done";
265#endif
266}
267
268bool QAudioEnginePrivate::isLoading() const
269{
270 return m_sampleLoader->isLoading();
271}
272
273QSoundSource* QAudioEnginePrivate::createSoundSource()
274{
275#ifdef DEBUG_AUDIOENGINE
276 qDebug() << "QAudioEnginePrivate::createSoundSource()";
277#endif
278 QSoundSourcePrivate *instance = NULL;
279 if (m_instancePool.count() == 0) {
280 instance = new QSoundSourcePrivate(this);
281 } else {
282 instance = m_instancePool.front();
283 m_instancePool.pop_front();
284 }
285 connect(instance, SIGNAL(activate(QObject*)), this, SLOT(soundSourceActivate(QObject*)));
286 return instance;
287}
288
289void QAudioEnginePrivate::releaseSoundSource(QSoundSource *soundInstance)
290{
291 QSoundSourcePrivate *privInstance = static_cast<QSoundSourcePrivate*>(soundInstance);
292#ifdef DEBUG_AUDIOENGINE
293 qDebug() << "recycle soundInstance" << privInstance;
294#endif
295 privInstance->unbindBuffer();
296 m_instancePool.push_front(privInstance);
297 m_activeInstances.removeOne(privInstance);
298}
299
300QSoundBuffer* QAudioEnginePrivate::getStaticSoundBuffer(const QUrl& url)
301{
302 StaticSoundBufferAL *staticBuffer = NULL;
303 QMap<QUrl, QSoundBufferPrivateAL*>::iterator it = m_staticBufferPool.find(url);
304 if (it == m_staticBufferPool.end()) {
305 staticBuffer = new StaticSoundBufferAL(this, url, m_sampleLoader);
306 m_staticBufferPool.insert(url, staticBuffer);
307 } else {
308 staticBuffer = static_cast<StaticSoundBufferAL*>(*it);
309 staticBuffer->addRef();
310 }
311 return staticBuffer;
312}
313
314void QAudioEnginePrivate::releaseSoundBuffer(QSoundBuffer *buffer)
315{
316#ifdef DEBUG_AUDIOENGINE
317 qDebug() << "QAudioEnginePrivate: recycle sound buffer";
318#endif
319 if (StaticSoundBufferAL *staticBuffer = qobject_cast<StaticSoundBufferAL *>(buffer)) {
320 //decrement the reference count, still kept in memory for reuse
321 staticBuffer->release();
322 //TODO implement some resource recycle strategy
323 } else {
324 //TODO
325 Q_ASSERT(0);
326 qWarning() << "Unknown soundbuffer type for recycle" << buffer;
327 }
328}
329
330bool QAudioEnginePrivate::checkNoError(const char *msg)
331{
332 ALenum error = alGetError();
333 if (error != AL_NO_ERROR) {
334 qWarning() << "Failed on" << msg << "[OpenAL error code =" << error << "]";
335 return false;
336 }
337 return true;
338}
339
340QVector3D QAudioEnginePrivate::listenerPosition() const
341{
342 ALfloat x, y, z;
343 alGetListener3f(AL_POSITION, &x, &y, &z);
344 checkNoError("get listener position");
345 return QVector3D(x, y, z);
346}
347
348QVector3D QAudioEnginePrivate::listenerVelocity() const
349{
350 ALfloat x, y, z;
351 alGetListener3f(AL_VELOCITY, &x, &y, &z);
352 checkNoError("get listener velocity");
353 return QVector3D(x, y, z);
354}
355
356qreal QAudioEnginePrivate::listenerGain() const
357{
358 ALfloat gain;
359 alGetListenerf(AL_GAIN, &gain);
360 checkNoError("get listener gain");
361 return gain;
362}
363
364void QAudioEnginePrivate::setListenerPosition(const QVector3D& position)
365{
366 alListener3f(AL_POSITION, position.x(), position.y(), position.z());
367 checkNoError("set listener position");
368}
369
370void QAudioEnginePrivate::setListenerOrientation(const QVector3D& direction, const QVector3D& up)
371{
372 ALfloat orientation[6];
373 orientation[0] = direction.x();
374 orientation[1] = direction.y();
375 orientation[2] = direction.z();
376 orientation[3] = up.x();
377 orientation[4] = up.y();
378 orientation[5] = up.z();
379 alListenerfv(AL_ORIENTATION, orientation);
380 checkNoError("set listener orientation");
381}
382
383void QAudioEnginePrivate::setListenerVelocity(const QVector3D& velocity)
384{
385 alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z());
386 checkNoError("set listener velocity");
387}
388
389void QAudioEnginePrivate::setListenerGain(qreal gain)
390{
391 alListenerf(AL_GAIN, gain);
392 checkNoError("set listener gain");
393}
394
395void QAudioEnginePrivate::setDopplerFactor(qreal dopplerFactor)
396{
397 alDopplerFactor(dopplerFactor);
398}
399
400void QAudioEnginePrivate::setSpeedOfSound(qreal speedOfSound)
401{
402 alSpeedOfSound(speedOfSound);
403}
404
405void QAudioEnginePrivate::soundSourceActivate(QObject *soundSource)
406{
407 QSoundSourcePrivate *ss = qobject_cast<QSoundSourcePrivate*>(soundSource);
408 ss->checkState();
409 if (ss->isLooping())
410 return;
411 if (!m_activeInstances.contains(ss))
412 m_activeInstances.push_back(ss);
413 if (!m_updateTimer.isActive())
414 m_updateTimer.start();
415}
416
417void QAudioEnginePrivate::updateSoundSources()
418{
419 for (QList<QSoundSourcePrivate*>::Iterator it = m_activeInstances.begin();
420 it != m_activeInstances.end();) {
421 QSoundSourcePrivate *instance = *it;
422 instance->checkState();
423 if (instance->state() == QSoundSource::StoppedState) {
424 it = m_activeInstances.erase(it);
425 } else {
426 ++it;
427 }
428 }
429
430 if (m_activeInstances.count() == 0) {
431 m_updateTimer.stop();
432 }
433}
434