Better_Software_Header_MobileBetter_Software_Header_Web

Find what you need - explore our website and developer resources

Implementing an Audio Mixer, Part 2

Basic DSP with Qt Multimedia

const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice();
const QAudioFormat &format = device.preferredFormat();
static QAudioOutput audioOutput(device, format);
virtual qint64 QIODevice::readData(char *data, qint64 maxSize);
virtual qint64 QIODevice::writeData(const char *data, qint64 maxSize);
// mixerstream.h

#pragma once

#include <QAudioFormat>
#include <QAudioOutput>
#include <QIODevice>

class DecodeStream;

class MixerStream : public QIODevice
{
    Q_OBJECT

public:
    explicit MixerStream(const QAudioFormat &format);
    ~MixerStream();

    void openStream(const QString &fileName, int loops = 1);

protected:
    qint64 readData(char *data, qint64 maxSize) override;
    qint64 writeData(const char *data, qint64 maxSize) override;

private:
    QAudioFormat mFormat;
    QList<DecodeStream *> mStreams;
};
// mixer.h

#pragma once

#include <QtGlobal>

#include <limits>

namespace Mixer
{
inline qint16 combineSamples(qint32 samp1, qint32 samp2)
{
    const auto sum = samp1 + samp2;
    if (std::numeric_limits<qint16>::max() < sum)
        return std::numeric_limits<qint16>::max();

    if (std::numeric_limits<qint16>::min() > sum)
        return std::numeric_limits<qint16>::min();

    return sum;
}
} // namespace Mixer
explicit MixerStream(const QAudioFormat &format);
~MixerStream();

void openStream(const QString &fileName, int loops = 1);

qint64 readData(char *data, qint64 maxSize) override;
qint64 writeData(const char *data, qint64 maxSize) override;
MixerStream::MixerStream(const QAudioFormat &format)
    : mFormat(format)
{
    setOpenMode(QIODevice::ReadOnly);
}
qint64 MixerStream::writeData([[maybe_unused]] const char *data,
                              [[maybe_unused]] qint64 maxSize)
{
    Q_ASSERT_X(false, "writeData", "not implemented");
    return 0;
}
MixerStream::~MixerStream()
{
    while (!mStreams.empty())
        delete mStreams.takeLast();
}
qint64 MixerStream::readData(char *data, qint64 maxSize)
{
    memset(data, 0, maxSize);

    constexpr qint16 bitDepth = sizeof(qint16);
    const qint16 numSamples = maxSize / bitDepth;

    for (auto *stream : qAsConst(mStreams))
    {
        auto *cursor = reinterpret_cast<qint16 *>(data);
        qint16 sample;

        for (int i = 0; i < numSamples; ++i, ++cursor)
            if (stream->read(reinterpret_cast<char *>(&sample), bitDepth))
                *cursor = Mixer::combineSamples(sample, *cursor);
    }

    return maxSize;
}
DecodeStream(fileName, mFormat, loops);
void MixerStream::openStream(const QString &fileName, int loops)
{
    auto *decodeStream = new DecodeStream(fileName, mFormat, loops);
    mStreams << decodeStream;

    connect(decodeStream, &DecodeStream::finished, this, [this, decodeStream]() {
        mStreams.removeAll(decodeStream);
        decodeStream->deleteLater();
    });
}
const QAudioBuffer buffer = mDecoder.read();
mInputBuf.write(buffer.data<char>(), buffer.byteCount());
#pragma once

#include <QtGlobal>

#include <limits>

namespace Mixer
{
enum State
{
    Playing,
    Stopped
};

inline qint16 combineSamples(qint32 samp1, qint32 samp2)
{
    const auto sum = samp1 + samp2;

    if (std::numeric_limits<qint16>::max() < sum)
        return std::numeric_limits<qint16>::max();

    if (std::numeric_limits<qint16>::min() > sum)
        return std::numeric_limits<qint16>::min();

    return sum;
}
} // namespace Mixer
// decodestream.h

#pragma once

#include "mixer.h"

#include <QAudioDecoder>
#include <QBuffer>
#include <QFile>

class DecodeStream : public QIODevice
{
    Q_OBJECT

public:
    explicit DecodeStream(const QString &fileName, const QAudioFormat &format, int loops);

protected:
    qint64 readData(char *data, qint64 maxSize) override;
    qint64 writeData(const char *data, qint64 maxSize) override;

signals:
    void finished();

private:
    QFile mSourceFile;
    QByteArray mData;
    QBuffer mInputBuf;
    QBuffer mOutputBuf;
    QAudioDecoder mDecoder;
    QAudioFormat mFormat;
    Mixer::State mState;
    int mLoopsLeft;
};
DecodeStream::DecodeStream(const QString &fileName, const QAudioFormat &format, int loops)
    : mSourceFile(fileName)
    , mInputBuf(&mData)
    , mOutputBuf(&mData)
    , mFormat(format)
    , mState(Mixer::Playing)
    , mLoopsLeft(loops)
{
    setOpenMode(QIODevice::ReadOnly);

    const bool valid = mSourceFile.open(QIODevice::ReadOnly) &&
                       mOutputBuf.open(QIODevice::ReadOnly) &&
                       mInputBuf.open(QIODevice::WriteOnly);

    Q_ASSERT(valid);

    mDecoder.setAudioFormat(mFormat);
    mDecoder.setSourceDevice(&mSourceFile);
    mDecoder.start();

    connect(&mDecoder, &QAudioDecoder::bufferReady, this, [this]() {
        const QAudioBuffer buffer = mDecoder.read();
        mInputBuf.write(buffer.data<char>(), buffer.byteCount());
    });
}
qint64 DecodeStream::writeData([[maybe_unused]] const char *data,
                               [[maybe_unused]] qint64 maxSize)
{
    Q_ASSERT_X(false, "writeData", "not implemented");
    return 0;
}
qint64 DecodeStream::readData(char *data, qint64 maxSize)
{
    memset(data, 0, maxSize);

    if (mState == Mixer::Playing)
    {
        mOutputBuf.read(data, maxSize);
        if (mOutputBuf.size() && mOutputBuf.atEnd())
        {
            if (--mLoopsLeft == 0)
            {
                mState = Mixer::Stopped;
                emit finished();
            }

            mOutputBuf.seek(0);
        }
    }

    return maxSize;
}
const auto &device = QAudioDeviceInfo::defaultOutputDevice();
const auto &format = device.preferredFormat();

auto mixerStream = std::make_unique<MixerStream>(format);

auto *audioOutput = new QAudioOutput(device, format);
audioOutput->setVolume(0.5);
audioOutput->start(mixerStream.get());

mixerStream->openStream(QStringLiteral("/path/to/some/sound.wav"));
mixerStream->openStream(QStringLiteral("/path/to/something/else.mp3"), 3);

About KDAB