diff options
Diffstat (limited to 'src/multimedia/wasm/qwasmaudiosource.cpp')
-rw-r--r-- | src/multimedia/wasm/qwasmaudiosource.cpp | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/src/multimedia/wasm/qwasmaudiosource.cpp b/src/multimedia/wasm/qwasmaudiosource.cpp new file mode 100644 index 000000000..81f222c4b --- /dev/null +++ b/src/multimedia/wasm/qwasmaudiosource.cpp @@ -0,0 +1,315 @@ +// 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 <emscripten.h> +#include <AL/al.h> +#include <AL/alc.h> +#include <QDataStream> +#include <QDebug> +#include <QtMath> +#include <private/qaudiohelpers_p.h> +#include <QIODevice> + +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)) +{ + if (device.contains("Emscripten")) { + 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) / 3000); + m_timer->start(); + + alcGetError(aldata->device); // clear error state + 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; +} + +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); +} + +qsizetype 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(qsizetype value) +{ + if (!m_running) + return; + m_bufferSize = value; +} + +qsizetype 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 |