// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwasmaudiosource_p.h" #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE #define AL_FORMAT_MONO_FLOAT32 0x10010 #define AL_FORMAT_STEREO_FLOAT32 0x10011 constexpr unsigned int DEFAULT_BUFFER_DURATION = 50'000; class QWasmAudioSourceDevice : public QIODevice { QWasmAudioSource *m_in; public: explicit QWasmAudioSourceDevice(QWasmAudioSource *in); protected: qint64 readData(char *data, qint64 maxlen) override; qint64 writeData(const char *data, qint64 len) override; }; class ALData { public: ALCdevice *device = nullptr; ALCcontext *context = nullptr; }; void QWasmAudioSource::setError(const QAudio::Error &error) { if (m_error == error) return; m_error = error; emit errorChanged(error); } void QWasmAudioSource::writeBuffer() { int samples = 0; alcGetIntegerv(aldata->device, ALC_CAPTURE_SAMPLES, 1, &samples); samples = qMin(samples, m_format.framesForBytes(m_bufferSize)); auto bytes = m_format.bytesForFrames(samples); auto err = alcGetError(aldata->device); alcCaptureSamples(aldata->device, m_tmpData, samples); err = alcGetError(aldata->device); if (err) { qWarning() << alcGetString(aldata->device, err); return setError(QAudio::FatalError); } if (m_volume < 1) QAudioHelperInternal::qMultiplySamples(m_volume, m_format, m_tmpData, m_tmpData, bytes); m_processed += bytes; m_device->write(m_tmpData,bytes); } QWasmAudioSource::QWasmAudioSource(const QByteArray &device , QObject *parent) : QPlatformAudioSource(parent), m_name(device), m_timer(new QTimer(this)) { aldata = new ALData(); connect(m_timer, &QTimer::timeout, this, [this](){ Q_ASSERT(m_running); if (m_pullMode) writeBuffer(); else if (bytesReady() > 0) emit m_device->readyRead(); }); } void QWasmAudioSource::start(QIODevice *device) { m_device = device; start(true); } QIODevice *QWasmAudioSource::start() { m_device = new QWasmAudioSourceDevice(this); m_device->open(QIODevice::ReadOnly); start(false); return m_device; } void QWasmAudioSource::start(bool mode) { m_pullMode = mode; auto formatError = [this](){ qWarning() << "Unsupported audio format " << m_format; setError(QAudio::OpenError); }; ALCenum format; switch (m_format.sampleFormat()) { case QAudioFormat::UInt8: switch (m_format.channelCount()) { case 1: format = AL_FORMAT_MONO8; break; case 2: format = AL_FORMAT_STEREO8; break; default: return formatError(); } break; case QAudioFormat::Int16: switch (m_format.channelCount()) { case 1: format = AL_FORMAT_MONO16; break; case 2: format = AL_FORMAT_STEREO16; break; default: return formatError(); } break; case QAudioFormat::Float: switch (m_format.channelCount()) { case 1: format = AL_FORMAT_MONO_FLOAT32; break; case 2: format = AL_FORMAT_STEREO_FLOAT32; break; default: return formatError(); } break; default: return formatError(); } if (m_tmpData) delete[] m_tmpData; if (m_pullMode) m_tmpData = new char[m_bufferSize]; else m_tmpData = nullptr; m_timer->setInterval(m_format.durationForBytes(m_bufferSize) / 3'000); aldata->device = alcCaptureOpenDevice(m_name.data(), m_format.sampleRate(), format, m_format.framesForBytes(m_bufferSize)); auto err = alcGetError(aldata->device); if (err) { qWarning() << "alcCaptureOpenDevice" << alcGetString(aldata->device, err); return setError(QAudio::OpenError); } alcCaptureStart(aldata->device); m_elapsedTimer.start(); auto cerr = alcGetError(aldata->device); if (cerr) { qWarning() << "alcCaptureStart" << alcGetString(aldata->device, cerr); return setError(QAudio::OpenError); } m_processed = 0; m_running = true; m_timer->start(); } void QWasmAudioSource::stop() { if (m_pullMode) writeBuffer(); alcCaptureStop(aldata->device); alcCaptureCloseDevice(aldata->device); m_elapsedTimer.invalidate(); if (m_tmpData) { delete[] m_tmpData; m_tmpData = nullptr; } if (!m_pullMode) m_device->deleteLater(); m_timer->stop(); m_running = false; } void QWasmAudioSource::reset() { stop(); if (m_tmpData) { delete[] m_tmpData; m_tmpData = nullptr; } m_running = false; m_processed = 0; m_error = QAudio::NoError; } void QWasmAudioSource::suspend() { if (!m_running) return; m_suspended = true; alcCaptureStop(aldata->device); } void QWasmAudioSource::resume() { if (!m_running) return; m_suspended = false; alcCaptureStart(aldata->device); } int QWasmAudioSource::bytesReady() const { if (!m_running) return 0; int samples; alcGetIntegerv(aldata->device, ALC_CAPTURE_SAMPLES, 1, &samples); return m_format.bytesForFrames(samples); } void QWasmAudioSource::setBufferSize(int value) { if (!m_running) return; m_bufferSize = value; } int QWasmAudioSource::bufferSize() const { return m_bufferSize; } qint64 QWasmAudioSource::processedUSecs() const { return m_format.durationForBytes(m_processed); } QAudio::Error QWasmAudioSource::error() const { return m_error; } QAudio::State QWasmAudioSource::state() const { if (m_running) return QAudio::ActiveState; else return QAudio::StoppedState; } void QWasmAudioSource::setFormat(const QAudioFormat &fmt) { m_format = fmt; m_bufferSize = m_format.bytesForDuration(DEFAULT_BUFFER_DURATION); } QAudioFormat QWasmAudioSource::format() const { return m_format; } void QWasmAudioSource::setVolume(qreal volume) { m_volume = volume; } qreal QWasmAudioSource::volume() const { return m_volume; } QWasmAudioSourceDevice::QWasmAudioSourceDevice(QWasmAudioSource *in) : QIODevice(in), m_in(in) { } qint64 QWasmAudioSourceDevice::readData(char *data, qint64 maxlen) { int samples; alcGetIntegerv(m_in->aldata->device, ALC_CAPTURE_SAMPLES, 1, &samples); samples = qMin(samples, m_in->m_format.framesForBytes(maxlen)); auto bytes = m_in->m_format.bytesForFrames(samples); alcGetError(m_in->aldata->device); alcCaptureSamples(m_in->aldata->device, data, samples); if (m_in->m_volume < 1) QAudioHelperInternal::qMultiplySamples(m_in->m_volume, m_in->m_format, data, data, bytes); auto err = alcGetError(m_in->aldata->device); if (err) { qWarning() << alcGetString(m_in->aldata->device, err); m_in->setError(QAudio::FatalError); return 0; } m_in->m_processed += bytes; return bytes; } qint64 QWasmAudioSourceDevice::writeData(const char *data, qint64 len) { Q_UNREACHABLE(); Q_UNUSED(data); Q_UNUSED(len); return 0; } QT_END_NAMESPACE