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
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
4#include "qwasmaudiosink_p.h"
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;
24 ALuint source;
25 ALuint *buffers = nullptr;
26 ALuint *buffer = nullptr;
27 ALenum format;
28};
29
31
33
34 QWasmAudioSink *m_out;
35
36public:
38
39protected:
40 qint64 readData(char *data, qint64 maxlen) override;
41 qint64 writeData(const char *data, qint64 len) override;
42};
43
45 : QPlatformAudioSink(parent),
46 m_name(device),
47 m_timer(new QTimer(this))
48{
49 m_timer->setSingleShot(false);
50 aldata = new ALData();
51 connect(m_timer, &QTimer::timeout, this, [this](){
52 if (m_pullMode)
53 nextALBuffers();
54 else {
55 unloadALBuffers();
56 m_device->write(nullptr, 0);
57 updateState();
58 }
59 });
60}
61
63{
64 delete aldata;
65 if (m_tmpData)
66 delete[] m_tmpData;
67}
68
70{
72 Q_ASSERT(device->openMode().testFlag(QIODevice::ReadOnly));
73 m_device = device;
74 start(true);
75}
76
78{
79 m_device = new QWasmAudioSinkDevice(this);
80 m_device->open(QIODevice::WriteOnly);
81 start(false);
82 return m_device;
83}
84
86{
87 auto formatError = [this](){
88 qWarning() << "Unsupported audio format " << m_format;
89 setError(QAudio::OpenError);
90 };
91 switch (m_format.sampleFormat()) {
93 switch (m_format.channelCount()) {
94 case 1:
95 aldata->format = AL_FORMAT_MONO8;
96 break;
97 case 2:
98 aldata->format = AL_FORMAT_STEREO8;
99 break;
100 default:
101 return formatError();
102 }
103 break;
105 switch (m_format.channelCount()) {
106 case 1:
107 aldata->format = AL_FORMAT_MONO16;
108 break;
109 case 2:
110 aldata->format = AL_FORMAT_STEREO16;
111 break;
112 default:
113 return formatError();
114 }
115 break;
117 switch (m_format.channelCount()) {
118 case 1:
120 break;
121 case 2:
123 break;
124 default:
125 return formatError();
126 }
127 break;
128 default:
129 return formatError();
130 }
131
132 alGetError();
133 aldata->device = alcOpenDevice(m_name.data());
134 if (!aldata->device) {
135 qWarning() << "Failed to open audio device" << alGetString(alGetError());
136 return setError(QAudio::OpenError);
137 }
138 ALint attrlist[] = {ALC_FREQUENCY, m_format.sampleRate(), 0};
139 aldata->context = alcCreateContext(aldata->device, attrlist);
140
141 if (!aldata->context) {
142 qWarning() << "Failed to create audio context" << alGetString(alGetError());
143 return setError(QAudio::OpenError);
144 }
145 alcMakeContextCurrent(aldata->context);
146
147 alGenSources(1, &aldata->source);
148
149 if (m_bufferSize > 0)
150 m_bufferFragmentsCount = qMax(2,qCeil((qreal)m_bufferSize/(m_bufferFragmentSize)));
151 m_bufferSize = m_bufferFragmentsCount * m_bufferFragmentSize;
152 aldata->buffers = new ALuint[m_bufferFragmentsCount];
153 aldata->buffer = aldata->buffers;
154 alGenBuffers(m_bufferFragmentsCount, aldata->buffers);
155 m_processed = 0;
156 m_tmpDataOffset = 0;
157 m_pullMode = mode;
158 alSourcef(aldata->source, AL_GAIN, m_volume);
159 if (m_pullMode)
160 loadALBuffers();
161 m_timer->setInterval(DEFAULT_BUFFER_DURATION / 3000);
162 m_timer->start();
163 if (m_pullMode)
164 alSourcePlay(aldata->source);
165 m_running = true;
166 m_elapsedTimer.start();
167 updateState();
168}
169
171{
172 if (!m_running)
173 return;
174 m_elapsedTimer.invalidate();
175 alSourceStop(aldata->source);
176 alSourceRewind(aldata->source);
177 m_timer->stop();
178 m_bufferFragmentsBusyCount = 0;
179 alDeleteSources(1, &aldata->source);
180 alDeleteBuffers(m_bufferFragmentsCount, aldata->buffers);
181 delete[] aldata->buffers;
182 alcMakeContextCurrent(nullptr);
183 alcDestroyContext(aldata->context);
184 alcCloseDevice(aldata->device);
185 m_running = false;
186 m_processed = 0;
187 if (!m_pullMode)
188 m_device->deleteLater();
189 updateState();
190}
191
193{
194 stop();
195 m_error = QAudio::NoError;
196}
197
199{
200 if (!m_running)
201 return;
202
203 m_suspendedInState = m_state;
204 alSourcePause(aldata->source);
205}
206
208{
209 if (!m_running)
210 return;
211
212 alSourcePlay(aldata->source);
213}
214
216{
217 int processed;
218 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
219 return m_running ? m_bufferFragmentSize * (m_bufferFragmentsCount - m_bufferFragmentsBusyCount
220 + (qsizetype)processed) : 0;
221}
222
224{
225 if (m_running)
226 return;
227
228 m_bufferSize = value;
229}
230
232{
233 return m_bufferSize;
234}
235
237{
238 int processed;
239 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
240 return m_format.durationForBytes(m_processed + m_format.bytesForDuration(
241 DEFAULT_BUFFER_DURATION * processed));
242}
243
245{
246 return m_error;
247}
248
250{
251 if (!m_running)
253 ALint state;
254 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
255 switch (state) {
256 case AL_INITIAL:
257 return QAudio::IdleState;
258 case AL_PLAYING:
259 return QAudio::ActiveState;
260 case AL_PAUSED:
262 case AL_STOPPED:
263 return m_running ? QAudio::IdleState : QAudio::StoppedState;
264 }
266}
267
269{
270 if (m_running)
271 return;
272 m_format = fmt;
273 if (m_tmpData)
274 delete[] m_tmpData;
275 m_bufferFragmentSize = m_format.bytesForDuration(DEFAULT_BUFFER_DURATION);
276 m_bufferSize = m_bufferFragmentSize * m_bufferFragmentsCount;
277 m_tmpData = new char[m_bufferFragmentSize];
278}
279
281{
282 return m_format;
283}
284
286{
287 if (m_volume == volume)
288 return;
289 m_volume = volume;
290 if (m_running)
291 alSourcef(aldata->source, AL_GAIN, volume);
292}
293
295{
296 return m_volume;
297}
298
299void QWasmAudioSink::loadALBuffers()
300{
301 if (m_bufferFragmentsBusyCount == m_bufferFragmentsCount)
302 return;
303
304 if (m_device->bytesAvailable() == 0) {
305 return;
306 }
307
308 auto size = m_device->read(m_tmpData + m_tmpDataOffset, m_bufferFragmentSize -
309 m_tmpDataOffset);
310 m_tmpDataOffset += size;
311 if (!m_tmpDataOffset || (m_tmpDataOffset != m_bufferFragmentSize &&
312 m_bufferFragmentsBusyCount >= m_bufferFragmentsCount * 2 / 3))
313 return;
314
315 alBufferData(*aldata->buffer, aldata->format, m_tmpData, m_tmpDataOffset,
316 m_format.sampleRate());
317 m_tmpDataOffset = 0;
318 alGetError();
319 alSourceQueueBuffers(aldata->source, 1, aldata->buffer);
320 if (alGetError())
321 return;
322
323 m_bufferFragmentsBusyCount++;
324 m_processed += size;
325 if (++aldata->buffer == aldata->buffers + m_bufferFragmentsCount)
326 aldata->buffer = aldata->buffers;
327}
328
329void QWasmAudioSink::unloadALBuffers()
330{
331 int processed;
332 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
333
334 if (processed) {
335 auto head = aldata->buffer - m_bufferFragmentsBusyCount;
336 if (head < aldata->buffers) {
337 if (head + processed > aldata->buffers) {
338 auto batch = m_bufferFragmentsBusyCount - (aldata->buffer - aldata->buffers);
339 alGetError();
340 alSourceUnqueueBuffers(aldata->source, batch, head + m_bufferFragmentsCount);
341 if (!alGetError()) {
342 m_bufferFragmentsBusyCount -= batch;
343 m_processed += m_bufferFragmentSize*batch;
344 }
345 processed -= batch;
346 if (!processed)
347 return;
348 head = aldata->buffers;
349 } else {
350 head += m_bufferFragmentsCount;
351 }
352 }
353 alGetError();
354 alSourceUnqueueBuffers(aldata->source, processed, head);
355 if (!alGetError())
356 m_bufferFragmentsBusyCount -= processed;
357 }
358}
359
360void QWasmAudioSink::nextALBuffers()
361{
362 updateState();
363 unloadALBuffers();
364 loadALBuffers();
365 ALint state;
366 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
367 if (state != AL_PLAYING && m_error == QAudio::NoError)
368 alSourcePlay(aldata->source);
369 updateState();
370}
371
372void QWasmAudioSink::updateState()
373{
374 auto current = state();
375 if (m_state == current)
376 return;
377 m_state = current;
378
379 if (m_state == QAudio::IdleState && m_running && m_device->bytesAvailable() == 0)
380 setError(QAudio::UnderrunError);
381
382 emit stateChanged(m_state);
383
384}
385
386void QWasmAudioSink::setError(QAudio::Error error)
387{
388 if (error == m_error)
389 return;
390 m_error = error;
391 if (error != QAudio::NoError) {
392 m_timer->stop();
393 alSourceRewind(aldata->source);
394 }
395
397}
398
400 : QIODevice(parent),
401 m_out(parent)
402{
403}
404
406{
408 Q_UNUSED(maxlen)
409 return 0;
410}
411
412
414{
415 ALint state;
416 alGetSourcei(m_out->aldata->source, AL_SOURCE_STATE, &state);
417 if (state != AL_INITIAL)
418 m_out->unloadALBuffers();
419 if (m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount) {
420 bool exceeds = m_out->m_tmpDataOffset + len > m_out->m_bufferFragmentSize;
421 bool flush = m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount * 2 / 3 ||
422 m_out->m_tmpDataOffset + len >= m_out->m_bufferFragmentSize;
423 const char *read;
424 char *tmp = m_out->m_tmpData;
425 int size = 0;
426 if (m_out->m_tmpDataOffset && exceeds) {
427 size = m_out->m_tmpDataOffset + len;
428 tmp = new char[m_out->m_tmpDataOffset + len];
429 std::memcpy(tmp, m_out->m_tmpData, m_out->m_tmpDataOffset);
430 }
431 if (flush && !m_out->m_tmpDataOffset) {
432 read = data;
433 size = len;
434 } else {
435 std::memcpy(tmp + m_out->m_tmpDataOffset, data, len);
436 read = tmp;
437 if (!exceeds) {
438 m_out->m_tmpDataOffset += len;
439 size = m_out->m_tmpDataOffset;
440 }
441 }
442 m_out->m_processed += size;
443 if (size && flush) {
444 alBufferData(*m_out->aldata->buffer, m_out->aldata->format, read, size,
445 m_out->m_format.sampleRate());
446 if (tmp && tmp != m_out->m_tmpData)
447 delete[] tmp;
448 m_out->m_tmpDataOffset = 0;
449 alGetError();
450 alSourceQueueBuffers(m_out->aldata->source, 1, m_out->aldata->buffer);
451 if (alGetError())
452 return 0;
453 m_out->m_bufferFragmentsBusyCount++;
454 if (++m_out->aldata->buffer == m_out->aldata->buffers + m_out->m_bufferFragmentsCount)
455 m_out->aldata->buffer = m_out->aldata->buffers;
456 if (state != AL_PLAYING)
457 alSourcePlay(m_out->aldata->source);
458 }
459 return len;
460 }
461 return 0;
462}
463
IOBluetoothDevice * device
ALenum format
ALCcontext * context
ALuint * buffers
ALCdevice * device
ALuint source
ALuint * buffer
The QAudioFormat class stores audio stream parameter information.
constexpr int channelCount() const noexcept
Returns the current channel count value.
constexpr int sampleRate() const noexcept
Returns the current sample rate in Hertz.
constexpr SampleFormat sampleFormat() const noexcept
Returns the current sample format.
Q_MULTIMEDIA_EXPORT qint32 bytesForDuration(qint64 microseconds) const
Returns the number of bytes required for this audio format for microseconds.
Q_MULTIMEDIA_EXPORT qint64 durationForBytes(qint32 byteCount) const
Returns the number of microseconds represented by bytes in this format.
void stateChanged(QAudio::State state)
void errorChanged(QAudio::Error error)
\inmodule QtCore
Definition qbytearray.h:57
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
void invalidate() noexcept
Marks this QElapsedTimer object as invalid.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual bool open(QIODeviceBase::OpenMode mode)
Opens the device and sets its OpenMode to mode.
virtual qint64 size() const
For open random-access devices, this function returns the size of the device.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
virtual qint64 bytesAvailable() const
Returns the number of bytes that are available for reading.
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
\inmodule QtCore
Definition qtimer.h:20
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
void setInterval(int msec)
Definition qtimer.cpp:579
void stop()
Stops the timer.
Definition qtimer.cpp:267
void timeout(QPrivateSignal)
This signal is emitted when the timer times out.
qint64 readData(char *data, qint64 maxlen) override
Reads up to maxSize bytes from the device into data, and returns the number of bytes read or -1 if an...
qint64 writeData(const char *data, qint64 len) override
Writes up to maxSize bytes from data to the device.
QWasmAudioSinkDevice(QWasmAudioSink *parent)
void resume() override
void stop() override
qreal volume() const override
void setVolume(qreal volume) override
void suspend() override
void setFormat(const QAudioFormat &fmt) override
QWasmAudioSink(const QByteArray &device, QObject *parent)
QIODevice * start() override
QAudio::State state() const override
qsizetype bufferSize() const override
QAudio::Error error() const override
void setBufferSize(qsizetype value) override
friend class QWasmAudioSinkDevice
qsizetype bytesFree() const override
qint64 processedUSecs() const override
QAudioFormat format() const override
void reset() override
#define this
Definition dialogs.cpp:9
else opt state
[0]
State
Definition qaudio.h:29
@ StoppedState
Definition qaudio.h:29
@ SuspendedState
Definition qaudio.h:29
@ IdleState
Definition qaudio.h:29
@ ActiveState
Definition qaudio.h:29
Error
Definition qaudio.h:28
@ UnderrunError
Definition qaudio.h:28
@ OpenError
Definition qaudio.h:28
@ NoError
Definition qaudio.h:28
Combined button and popup list for selecting options.
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
int qCeil(T v)
Definition qmath.h:36
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint const GLuint * buffers
GLenum mode
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLenum GLsizei len
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
#define Q_UNUSED(x)
ptrdiff_t qsizetype
Definition qtypes.h:165
long long qint64
Definition qtypes.h:60
double qreal
Definition qtypes.h:187
QVideoFrameFormat::PixelFormat fmt
#define AL_FORMAT_STEREO_FLOAT32
#define AL_FORMAT_MONO_FLOAT32
constexpr unsigned int DEFAULT_BUFFER_DURATION
QByteArray readData()
manager head(request, this, [this](QRestReply &reply) { if(reply.isSuccess()) })
[6]