15#define AL_FORMAT_MONO_FLOAT32 0x10010
16#define AL_FORMAT_STEREO_FLOAT32 0x10011
32class QWasmAudioSinkDevice :
public QIODevice {
34 QWasmAudioSink *m_out;
37 QWasmAudioSinkDevice(QWasmAudioSink *parent);
40 qint64 readData(
char *data, qint64 maxlen) override;
41 qint64 writeData(
const char *data, qint64 len) override;
44QWasmAudioSink::QWasmAudioSink(QAudioDevice device,
const QAudioFormat &fmt, QObject *parent)
45 : QPlatformAudioSink(std::move(device), fmt, parent), m_timer(
new QTimer(
this))
47 m_timer->setSingleShot(
false);
48 aldata =
new ALData();
49 connect(m_timer, &QTimer::timeout,
this, [
this](){
54 m_device->write(
nullptr, 0);
59 m_bufferFragmentSize = m_format.bytesForDuration(DEFAULT_BUFFER_DURATION);
60 m_bufferSize = m_bufferFragmentSize * m_bufferFragmentsCount;
61 m_tmpData =
new char[m_bufferFragmentSize];
64QWasmAudioSink::~QWasmAudioSink()
71void QWasmAudioSink::start(QIODevice *device)
74 Q_ASSERT(device->openMode().testFlag(QIODevice::ReadOnly));
79QIODevice *QWasmAudioSink::start()
81 m_device =
new QWasmAudioSinkDevice(
this);
82 m_device->open(QIODevice::WriteOnly);
87void QWasmAudioSink::start(
bool mode)
89 auto formatError = [
this](){
90 qWarning() <<
"Unsupported audio format " << m_format;
91 setError(QAudio::OpenError);
93 switch (m_format.sampleFormat()) {
94 case QAudioFormat::UInt8:
95 switch (m_format.channelCount()) {
97 aldata->format = AL_FORMAT_MONO8;
100 aldata->format = AL_FORMAT_STEREO8;
103 return formatError();
106 case QAudioFormat::Int16:
107 switch (m_format.channelCount()) {
109 aldata->format = AL_FORMAT_MONO16;
112 aldata->format = AL_FORMAT_STEREO16;
115 return formatError();
118 case QAudioFormat::Float:
119 switch (m_format.channelCount()) {
127 return formatError();
131 return formatError();
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);
140 ALint attrlist[] = {ALC_FREQUENCY, m_format.sampleRate(), 0};
141 aldata->context = alcCreateContext(aldata->device, attrlist);
143 if (!aldata->context) {
144 qWarning() <<
"Failed to create audio context" << alGetString(alGetError());
145 return setError(QAudio::OpenError);
147 alcMakeContextCurrent(aldata->context);
149 alGenSources(1, &aldata->source);
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);
160 alSourcef(aldata->source, AL_GAIN, volume());
163 m_timer->setInterval(DEFAULT_BUFFER_DURATION / 3000);
166 alSourcePlay(aldata->source);
168 m_elapsedTimer.start();
172void QWasmAudioSink::stop()
176 m_elapsedTimer.invalidate();
177 alSourceStop(aldata->source);
178 alSourceRewind(aldata->source);
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);
190 m_device->deleteLater();
194void QWasmAudioSink::reset()
197 setError(QAudio::NoError);
200void QWasmAudioSink::suspend()
205 m_suspendedInState = m_state;
206 alSourcePause(aldata->source);
209void QWasmAudioSink::resume()
214 alSourcePlay(aldata->source);
217qsizetype QWasmAudioSink::bytesFree()
const
220 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
221 return m_running ? m_bufferFragmentSize * (m_bufferFragmentsCount - m_bufferFragmentsBusyCount
222 + (qsizetype)processed) : 0;
225void QWasmAudioSink::setBufferSize(qsizetype value)
230 m_bufferSize = value;
233qsizetype QWasmAudioSink::bufferSize()
const
238qint64 QWasmAudioSink::processedUSecs()
const
241 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &processed);
242 return m_format.durationForBytes(m_processed + m_format.bytesForDuration(
243 DEFAULT_BUFFER_DURATION * processed));
246QAudio::State QWasmAudioSink::state()
const
249 return QAudio::StoppedState;
251 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
254 return QAudio::IdleState;
256 return QAudio::ActiveState;
258 return QAudio::SuspendedState;
260 return m_running ? QAudio::IdleState : QAudio::StoppedState;
262 return QAudio::StoppedState;
265void QWasmAudioSink::setVolume(
float volume)
267 QPlatformAudioEndpointBase::setVolume(volume);
269 alSourcef(aldata->source, AL_GAIN, volume);
272void QWasmAudioSink::loadALBuffers()
274 if (m_bufferFragmentsBusyCount == m_bufferFragmentsCount)
277 if (m_device->bytesAvailable() == 0) {
281 auto size = m_device->read(m_tmpData + m_tmpDataOffset, m_bufferFragmentSize -
283 m_tmpDataOffset += size;
284 if (!m_tmpDataOffset || (m_tmpDataOffset != m_bufferFragmentSize &&
285 m_bufferFragmentsBusyCount >= m_bufferFragmentsCount * 2 / 3))
288 alBufferData(*aldata->buffer, aldata->format, m_tmpData, m_tmpDataOffset,
289 m_format.sampleRate());
292 alSourceQueueBuffers(aldata->source, 1, aldata->buffer);
296 m_bufferFragmentsBusyCount++;
298 if (++aldata->buffer == aldata->buffers + m_bufferFragmentsCount)
299 aldata->buffer = aldata->buffers;
302void QWasmAudioSink::unloadALBuffers()
305 alGetSourcei(aldata->source, AL_BUFFERS_PROCESSED, &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);
313 alSourceUnqueueBuffers(aldata->source, batch, head + m_bufferFragmentsCount);
315 m_bufferFragmentsBusyCount -= batch;
316 m_processed += m_bufferFragmentSize*batch;
321 head = aldata->buffers;
323 head += m_bufferFragmentsCount;
327 alSourceUnqueueBuffers(aldata->source, processed, head);
329 m_bufferFragmentsBusyCount -= processed;
333void QWasmAudioSink::nextALBuffers()
339 alGetSourcei(aldata->source, AL_SOURCE_STATE, &state);
340 if (state != AL_PLAYING && error() == QAudio::NoError)
341 alSourcePlay(aldata->source);
345void QWasmAudioSink::updateState()
347 auto current = state();
348 if (m_state == current)
352 emit stateChanged(m_state);
355void QWasmAudioSink::setError(QAudio::Error error)
357 QPlatformAudioEndpointBase::setError(error);
359 if (error != QAudio::NoError) {
361 alSourceRewind(aldata->source);
365QWasmAudioSinkDevice::QWasmAudioSinkDevice(QWasmAudioSink *parent)
371qint64 QWasmAudioSinkDevice::readData(
char *data, qint64 maxlen)
379qint64 QWasmAudioSinkDevice::writeData(
const char *data, qint64 len)
382 alGetSourcei(m_out->aldata->source, AL_SOURCE_STATE, &state);
383 if (state != AL_INITIAL)
384 m_out->unloadALBuffers();
385 if (m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount) {
386 bool exceeds = m_out->m_tmpDataOffset + len > m_out->m_bufferFragmentSize;
387 bool flush = m_out->m_bufferFragmentsBusyCount < m_out->m_bufferFragmentsCount * 2 / 3 ||
388 m_out->m_tmpDataOffset + len >= m_out->m_bufferFragmentSize;
390 char *tmp = m_out->m_tmpData;
392 if (m_out->m_tmpDataOffset && exceeds) {
393 size = m_out->m_tmpDataOffset + len;
394 tmp =
new char[m_out->m_tmpDataOffset + len];
395 std::memcpy(tmp, m_out->m_tmpData, m_out->m_tmpDataOffset);
397 if (flush && !m_out->m_tmpDataOffset) {
401 std::memcpy(tmp + m_out->m_tmpDataOffset, data, len);
404 m_out->m_tmpDataOffset += len;
405 size = m_out->m_tmpDataOffset;
408 m_out->m_processed += size;
410 alBufferData(*m_out->aldata->buffer, m_out->aldata->format, read, size,
411 m_out->m_format.sampleRate());
412 if (tmp && tmp != m_out->m_tmpData)
414 m_out->m_tmpDataOffset = 0;
416 alSourceQueueBuffers(m_out->aldata->source, 1, m_out->aldata->buffer);
419 m_out->m_bufferFragmentsBusyCount++;
420 if (++m_out->aldata->buffer == m_out->aldata->buffers + m_out->m_bufferFragmentsCount)
421 m_out->aldata->buffer = m_out->aldata->buffers;
422 if (state != AL_PLAYING)
423 alSourcePlay(m_out->aldata->source);
#define AL_FORMAT_STEREO_FLOAT32
#define AL_FORMAT_MONO_FLOAT32
constexpr unsigned int DEFAULT_BUFFER_DURATION