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
qwasmaudiosink.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6
7#include <emscripten.h>
8#include <AL/al.h>
9#include <AL/alc.h>
10#include <QDebug>
11#include <QtMath>
12#include <QIODevice>
13
14// non native al formats (AL_EXT_float32)
15#define AL_FORMAT_MONO_FLOAT32 0x10010
16#define AL_FORMAT_STEREO_FLOAT32 0x10011
17
18constexpr unsigned int DEFAULT_BUFFER_DURATION = 50'000;
19
20class ALData {
21public:
22 ALCcontext *context = nullptr;
23 ALCdevice *device = nullptr;
25 ALuint *buffers = nullptr;
26 ALuint *buffer = nullptr;
28};
29
30QT_BEGIN_NAMESPACE
31
32class QWasmAudioSinkDevice : public QIODevice {
33
34 QWasmAudioSink *m_out;
35
36public:
37 QWasmAudioSinkDevice(QWasmAudioSink *parent);
38
39protected:
40 qint64 readData(char *data, qint64 maxlen) override;
41 qint64 writeData(const char *data, qint64 len) override;
42};
43
44QWasmAudioSink::QWasmAudioSink(QAudioDevice device, const QAudioFormat &fmt, QObject *parent)
45 : QPlatformAudioSink(std::move(device), fmt, parent), m_timer(new QTimer(this))
46{
47 m_timer->setSingleShot(false);
48 aldata = new ALData();
49 connect(m_timer, &QTimer::timeout, this, [this](){
50 if (m_pullMode)
51 nextALBuffers();
52 else {
53 unloadALBuffers();
54 m_device->write(nullptr, 0);
55 updateState();
56 }
57 });
58
59 m_bufferFragmentSize = m_format.bytesForDuration(DEFAULT_BUFFER_DURATION);
60 m_bufferSize = m_bufferFragmentSize * m_bufferFragmentsCount;
61 m_tmpData = new char[m_bufferFragmentSize];
62}
63
64QWasmAudioSink::~QWasmAudioSink()
65{
66 delete aldata;
67 if (m_tmpData)
68 delete[] m_tmpData;
69}
70
71void QWasmAudioSink::start(QIODevice *device)
72{
73 Q_ASSERT(device);
74 Q_ASSERT(device->openMode().testFlag(QIODevice::ReadOnly));
75 m_device = device;
76 start(true);
77}
78
79QIODevice *QWasmAudioSink::start()
80{
81 m_device = new QWasmAudioSinkDevice(this);
82 m_device->open(QIODevice::WriteOnly);
83 start(false);
84 return m_device;
85}
86
87void QWasmAudioSink::start(bool mode)
88{
89 auto formatError = [this](){
90 qWarning() << "Unsupported audio format " << m_format;
91 setError(QAudio::OpenError);
92 };
93 switch (m_format.sampleFormat()) {
94 case QAudioFormat::UInt8:
95 switch (m_format.channelCount()) {
96 case 1:
97 aldata->format = AL_FORMAT_MONO8;
98 break;
99 case 2:
100 aldata->format = AL_FORMAT_STEREO8;
101 break;
102 default:
103 return formatError();
104 }
105 break;
106 case QAudioFormat::Int16:
107 switch (m_format.channelCount()) {
108 case 1:
109 aldata->format = AL_FORMAT_MONO16;
110 break;
111 case 2:
112 aldata->format = AL_FORMAT_STEREO16;
113 break;
114 default:
115 return formatError();
116 }
117 break;
118 case QAudioFormat::Float:
119 switch (m_format.channelCount()) {
120 case 1:
121 aldata->format = AL_FORMAT_MONO_FLOAT32;
122 break;
123 case 2:
124 aldata->format = AL_FORMAT_STEREO_FLOAT32;
125 break;
126 default:
127 return formatError();
128 }
129 break;
130 default:
131 return formatError();
132 }
133
134 alGetError();
135 aldata->device = alcOpenDevice(m_audioDevice.id().constData());
136 if (!aldata->device) {
137 qWarning() << "Failed to open audio device" << alGetString(alGetError());
138 return setError(QAudio::OpenError);
139 }
140 ALint attrlist[] = {ALC_FREQUENCY, m_format.sampleRate(), 0};
141 aldata->context = alcCreateContext(aldata->device, attrlist);
142
143 if (!aldata->context) {
144 qWarning() << "Failed to create audio context" << alGetString(alGetError());
145 return setError(QAudio::OpenError);
146 }
147 alcMakeContextCurrent(aldata->context);
148
149 alGenSources(1, &aldata->source);
150
151 if (m_bufferSize > 0)
152 m_bufferFragmentsCount = qMax(2,qCeil((qreal)m_bufferSize/(m_bufferFragmentSize)));
153 m_bufferSize = m_bufferFragmentsCount * m_bufferFragmentSize;
154 aldata->buffers = new ALuint[m_bufferFragmentsCount];
155 aldata->buffer = aldata->buffers;
156 alGenBuffers(m_bufferFragmentsCount, aldata->buffers);
157 m_processed = 0;
158 m_tmpDataOffset = 0;
159 m_pullMode = mode;
160 alSourcef(aldata->source, AL_GAIN, volume());
161 if (m_pullMode)
162 loadALBuffers();
163 m_timer->setInterval(DEFAULT_BUFFER_DURATION / 3000);
164 m_timer->start();
165 if (m_pullMode)
166 alSourcePlay(aldata->source);
167 m_running = true;
168 m_elapsedTimer.start();
169 updateState();
170}
171
172void QWasmAudioSink::stop()
173{
174 if (!m_running)
175 return;
176 m_elapsedTimer.invalidate();
177 alSourceStop(aldata->source);
178 alSourceRewind(aldata->source);
179 m_timer->stop();
180 m_bufferFragmentsBusyCount = 0;
181 alDeleteSources(1, &aldata->source);
182 alDeleteBuffers(m_bufferFragmentsCount, aldata->buffers);
183 delete[] aldata->buffers;
184 alcMakeContextCurrent(nullptr);
185 alcDestroyContext(aldata->context);
186 alcCloseDevice(aldata->device);
187 m_running = false;
188 m_processed = 0;
189 if (!m_pullMode)
190 m_device->deleteLater();
191 updateState();
192}
193
194void QWasmAudioSink::reset()
195{
196 stop();
197 setError(QAudio::NoError);
198}
199
200void QWasmAudioSink::suspend()
201{
202 if (!m_running)
203 return;
204
205 m_suspendedInState = m_state;
206 alSourcePause(aldata->source);
207}
208
209void QWasmAudioSink::resume()
210{
211 if (!m_running)
212 return;
213
214 alSourcePlay(aldata->source);
215}
216
217qsizetype QWasmAudioSink::bytesFree() const
218{
219 int processed;
220 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
221 return m_running ? m_bufferFragmentSize * (m_bufferFragmentsCount - m_bufferFragmentsBusyCount
222 + (qsizetype)processed) : 0;
223}
224
225void QWasmAudioSink::setBufferSize(qsizetype value)
226{
227 if (m_running)
228 return;
229
230 m_bufferSize = value;
231}
232
233qsizetype QWasmAudioSink::bufferSize() const
234{
235 return m_bufferSize;
236}
237
238qint64 QWasmAudioSink::processedUSecs() const
239{
240 int processed;
241 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
242 return m_format.durationForBytes(m_processed + m_format.bytesForDuration(
243 DEFAULT_BUFFER_DURATION * processed));
244}
245
246QAudio::State QWasmAudioSink::state() const
247{
248 if (!m_running)
249 return QAudio::StoppedState;
250 ALint state;
251 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
252 switch (state) {
253 case AL_INITIAL:
254 return QAudio::IdleState;
255 case AL_PLAYING:
256 return QAudio::ActiveState;
257 case AL_PAUSED:
258 return QAudio::SuspendedState;
259 case AL_STOPPED:
260 return m_running ? QAudio::IdleState : QAudio::StoppedState;
261 }
262 return QAudio::StoppedState;
263}
264
265void QWasmAudioSink::setVolume(float volume)
266{
267 QPlatformAudioEndpointBase::setVolume(volume);
268 if (m_running)
269 alSourcef(aldata->source, AL_GAIN, volume);
270}
271
272void QWasmAudioSink::loadALBuffers()
273{
274 if (m_bufferFragmentsBusyCount == m_bufferFragmentsCount)
275 return;
276
277 if (m_device->bytesAvailable() == 0) {
278 return;
279 }
280
281 auto size = m_device->read(m_tmpData + m_tmpDataOffset, m_bufferFragmentSize -
282 m_tmpDataOffset);
283 m_tmpDataOffset += size;
284 if (!m_tmpDataOffset || (m_tmpDataOffset != m_bufferFragmentSize &&
285 m_bufferFragmentsBusyCount >= m_bufferFragmentsCount * 2 / 3))
286 return;
287
288 alBufferData(*aldata->buffer, aldata->format, m_tmpData, m_tmpDataOffset,
289 m_format.sampleRate());
290 m_tmpDataOffset = 0;
291 alGetError();
292 alSourceQueueBuffers(aldata->source, 1, aldata->buffer);
293 if (alGetError())
294 return;
295
296 m_bufferFragmentsBusyCount++;
297 m_processed += size;
298 if (++aldata->buffer == aldata->buffers + m_bufferFragmentsCount)
299 aldata->buffer = aldata->buffers;
300}
301
302void QWasmAudioSink::unloadALBuffers()
303{
304 int processed;
305 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
306
307 if (processed) {
308 auto head = aldata->buffer - m_bufferFragmentsBusyCount;
309 if (head < aldata->buffers) {
310 if (head + processed > aldata->buffers) {
311 auto batch = m_bufferFragmentsBusyCount - (aldata->buffer - aldata->buffers);
312 alGetError();
313 alSourceUnqueueBuffers(aldata->source, batch, head + m_bufferFragmentsCount);
314 if (!alGetError()) {
315 m_bufferFragmentsBusyCount -= batch;
316 m_processed += m_bufferFragmentSize*batch;
317 }
318 processed -= batch;
319 if (!processed)
320 return;
321 head = aldata->buffers;
322 } else {
323 head += m_bufferFragmentsCount;
324 }
325 }
326 alGetError();
327 alSourceUnqueueBuffers(aldata->source, processed, head);
328 if (!alGetError())
329 m_bufferFragmentsBusyCount -= processed;
330 }
331}
332
333void QWasmAudioSink::nextALBuffers()
334{
335 updateState();
336 unloadALBuffers();
337 loadALBuffers();
338 ALint state;
339 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
340 if (state != AL_PLAYING && error() == QAudio::NoError)
341 alSourcePlay(aldata->source);
342 updateState();
343}
344
345void QWasmAudioSink::updateState()
346{
347 auto current = state();
348 if (m_state == current)
349 return;
350 m_state = current;
351
352 if (m_state == QAudio::IdleState && m_running && m_device->bytesAvailable() == 0)
353 setError(QAudio::UnderrunError);
354
355 emit stateChanged(m_state);
356
357}
358
359void QWasmAudioSink::setError(QAudio::Error error)
360{
361 QPlatformAudioEndpointBase::setError(error);
362
363 if (error != QAudio::NoError) {
364 m_timer->stop();
365 alSourceRewind(aldata->source);
366 }
367}
368
369QWasmAudioSinkDevice::QWasmAudioSinkDevice(QWasmAudioSink *parent)
370 : QIODevice(parent),
371 m_out(parent)
372{
373}
374
375qint64 QWasmAudioSinkDevice::readData(char *data, qint64 maxlen)
376{
377 Q_UNUSED(data)
378 Q_UNUSED(maxlen)
379 return 0;
380}
381
382
383qint64 QWasmAudioSinkDevice::writeData(const char *data, qint64 len)
384{
385 ALint state;
386 alGetSourcei(m_out->aldata->source, AL_SOURCE_STATE, &state);
387 if (state != AL_INITIAL)
388 m_out->unloadALBuffers();
389 if (m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount) {
390 bool exceeds = m_out->m_tmpDataOffset + len > m_out->m_bufferFragmentSize;
391 bool flush = m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount * 2 / 3 ||
392 m_out->m_tmpDataOffset + len >= m_out->m_bufferFragmentSize;
393 const char *read;
394 char *tmp = m_out->m_tmpData;
395 int size = 0;
396 if (m_out->m_tmpDataOffset && exceeds) {
397 size = m_out->m_tmpDataOffset + len;
398 tmp = new char[m_out->m_tmpDataOffset + len];
399 std::memcpy(tmp, m_out->m_tmpData, m_out->m_tmpDataOffset);
400 }
401 if (flush && !m_out->m_tmpDataOffset) {
402 read = data;
403 size = len;
404 } else {
405 std::memcpy(tmp + m_out->m_tmpDataOffset, data, len);
406 read = tmp;
407 if (!exceeds) {
408 m_out->m_tmpDataOffset += len;
409 size = m_out->m_tmpDataOffset;
410 }
411 }
412 m_out->m_processed += size;
413 if (size && flush) {
414 alBufferData(*m_out->aldata->buffer, m_out->aldata->format, read, size,
415 m_out->m_format.sampleRate());
416 if (tmp && tmp != m_out->m_tmpData)
417 delete[] tmp;
418 m_out->m_tmpDataOffset = 0;
419 alGetError();
420 alSourceQueueBuffers(m_out->aldata->source, 1, m_out->aldata->buffer);
421 if (alGetError())
422 return 0;
423 m_out->m_bufferFragmentsBusyCount++;
424 if (++m_out->aldata->buffer == m_out->aldata->buffers + m_out->m_bufferFragmentsCount)
425 m_out->aldata->buffer = m_out->aldata->buffers;
426 if (state != AL_PLAYING)
427 alSourcePlay(m_out->aldata->source);
428 }
429 return len;
430 }
431 return 0;
432}
433
434QT_END_NAMESPACE
ALenum format
ALCcontext * context
ALCdevice * device
ALuint * buffer
ALuint source
ALuint * buffers
#define AL_FORMAT_STEREO_FLOAT32
#define AL_FORMAT_MONO_FLOAT32
constexpr unsigned int DEFAULT_BUFFER_DURATION