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 if (m_state == QAudio::IdleState && m_running && m_device->bytesAvailable() == 0)
353 setError(QAudio::UnderrunError);
355 emit stateChanged(m_state);
359void QWasmAudioSink::setError(QAudio::Error error)
361 QPlatformAudioEndpointBase::setError(error);
363 if (error != QAudio::NoError) {
365 alSourceRewind(aldata->source);
369QWasmAudioSinkDevice::QWasmAudioSinkDevice(QWasmAudioSink *parent)
375qint64 QWasmAudioSinkDevice::readData(
char *data, qint64 maxlen)
383qint64 QWasmAudioSinkDevice::writeData(
const char *data, qint64 len)
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;
394 char *tmp = m_out->m_tmpData;
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);
401 if (flush && !m_out->m_tmpDataOffset) {
405 std::memcpy(tmp + m_out->m_tmpDataOffset, data, len);
408 m_out->m_tmpDataOffset += len;
409 size = m_out->m_tmpDataOffset;
412 m_out->m_processed += size;
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)
418 m_out->m_tmpDataOffset = 0;
420 alSourceQueueBuffers(m_out->aldata->source, 1, m_out->aldata->buffer);
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);
#define AL_FORMAT_STEREO_FLOAT32
#define AL_FORMAT_MONO_FLOAT32
constexpr unsigned int DEFAULT_BUFFER_DURATION