使用 QtAudioOutput 处理大型 QByteArray 导致 std::bad_alloc

Handeling large QByteArray with QtAudioOutput causes std::bad_alloc

使用 QAudioOut 我正在尝试按顺序播放存储在 QByteArray 中的数据...这在附加的数据很少时有效,但是当数据太多时,假设一个 2~3 小时的 RAW PCM 从不同的组合中将这些数据一次全部附加到 QByteArray 将导致 std::bad_alloc,因为堆不够大,无法同时保存所有数据时间。

我知道问题出在哪里,我想我有一个可能的解决方案,只是我不知道如何去实施它。

下面是一个转换后的函数,它接受列表中的值 第一个 440Hz 1800000 毫秒并创建 RAW PCM 方波。哪个有效然后将其附加到 QByteArray 然后播放它。

如果没有大量附加数据形成多个添加序列,这将起作用。

我正在寻找一种方法,可以从列表中一次执行一个操作,然后创建 wave,播放那个 wave x 毫秒,然后转到 MySeq 列表中的下一个条目。该列表可以包含运行数小时的 3 分钟频率的大型序列。

QStringList MySeq;

MySeq << "1:440:180000";
MySeq << "1:20:180000";
MySeq << "1:2120:180000";
MySeq << "1:240:180000";
MySeq << "1:570:180000";

foreach(QString seq, MySeq)
{
    QStringList Assits = seq.split(":");

    qDebug() << "Now At: " << seq;

    QString A = Assits.at(0);
    QString B = Assits.at(1);
    QString C = Assits.at(2);

    qreal amplitude = A.toInt();
    float frequency = B.toFloat();
    int msecs = C.toInt();

    qreal singleWaveTime = amplitude / frequency;

    qreal samplesPerWave = qCeil(format->sampleRate() * singleWaveTime);

    quint32 waveCount = qCeil(msecs / (singleWaveTime * 1000.0));

    quint32 sampleSize = static_cast<quint32>(format->sampleSize() / 8.0);

    QByteArray data(waveCount * samplesPerWave * sampleSize * format->channelCount(), '[=11=]');

    unsigned char* dataPointer = reinterpret_cast<unsigned char*>(data.data());

    for (quint32 currentWave = 0; currentWave < waveCount; currentWave++)
    {
        for (int currentSample = 0; currentSample < samplesPerWave; currentSample++)
        {
            double nextRadStep = (currentSample / static_cast<double>(samplesPerWave)) * (2 * M_PI);

            quint16 sampleValue = static_cast<quint16>((qSin(nextRadStep) > 0 ? 1 : -1) * 32767);

            for (int channel = 0; channel < format->channelCount(); channel++)
            {
                qToLittleEndian(sampleValue, dataPointer);
                dataPointer += sampleSize;
            }
        }
    }

    soundBuffer->append(data); // HERE IS THE Std::Bad_Alloc
    output->start(outputBuffer); 
    qDebug() << data.size()
}

我希望一次只用一个序列填充 QByteArray 然后用 QAudioOutput 播放然后清除 ByteArray 然后加载下一个序列重复直到完成所有序列在 QStringList

现在这种方法的问题是 QAudioOutput 是异步的,不会等待第一个序列完成 如果我像上面演示的那样遍历列表,它们会一个接一个地加载,实际上只播放最后一个频率。就像循环一样,不断覆盖之前的序列。

我不确定这里是否需要 QEventLoop(我还没有使用过的东西)或线程。我尝试了几种不同的方法但没有成功。任何建议将不胜感激。这是我对位于此处的波形文件和数据以及频率生成的以下问题的延续 Qt C++ Creating a square audio tone wave. Play and saving it

mainWindows.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtCore>
#include <QtMultimedia/QAudioOutput>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

public slots:

    void playbackFinished();

private slots:
    void appendSound();

    void on_pushButton_Run_clicked();

    void on_pushButton_Stop_clicked();

private:
    Ui::MainWindow *ui;

    QByteArray   *soundBuffer;
    QBuffer      *outputBuffer;
    QAudioFormat *format;
    QAudioOutput *output;
};

#endif // MAINWINDOW_H

mainWindows.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDebug>

int sampleRate = 44100;
int channelCount = 2;
int sampleSize = 16;
const QString codec = "audio/pcm";

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    soundBuffer = new QByteArray();

    format = new QAudioFormat();
    format->setSampleRate(sampleRate);
    format->setChannelCount(channelCount);
    format->setSampleSize(sampleSize);
    format->setCodec(codec);
    format->setByteOrder(QAudioFormat::LittleEndian);
    format->setSampleType(QAudioFormat::UnSignedInt);

    output = new QAudioOutput(*format, this);

    connect(output, SIGNAL(stateChanged(QAudio::State)), this, SLOT(playbackFinished()));

    outputBuffer = new QBuffer(soundBuffer);
    if (outputBuffer->open(QIODevice::ReadOnly) == false) {
        qCritical() << "Invalid operation while opening QBuffer. audio/pcm";
        return;
    }
}

MainWindow::~MainWindow()
{
    delete ui;
    delete soundBuffer;
    delete format;
    delete output;
    delete outputBuffer;
}

void MainWindow::playbackFinished()
{
    if (output->state() == QAudio::IdleState)
    {
        qDebug() << "Playback finished";
    }
}

void MainWindow::appendSound()
{

    QStringList MySq;

    MySq << "1:440:180000";
    MySq << "1:20:180000";
    MySq << "1:2120:180000";
    MySq << "1:240:180000";
    MySq << "1:570:180000";
    MySq << "1:570:180000";
    MySq << "1:570:180000";
    MySq << "1:850:180000";
    MySq << "1:1570:180000";
    MySq << "1:200:180000";
    MySq << "1:50:180000";
    MySq << "1:85:180000";
    MySq << "1:59:180000";
    MySq << "1:20:180000";

    foreach(QString seq, MySq)
    {
        QStringList Assits = seq.split(":");

        qDebug() << "Now At: " << seq;

        QString A = Assits.at(0);
        QString B = Assits.at(1);
        QString C = Assits.at(2);

        qreal amplitude = A.toInt();
        float frequency = B.toFloat();
        int msecs = C.toInt();

        msecs = (msecs < 50) ? 50 : msecs;

        qreal singleWaveTime = amplitude / frequency;

        qreal samplesPerWave = qCeil(format->sampleRate() * singleWaveTime);

        quint32 waveCount = qCeil(msecs / (singleWaveTime * 1000.0));

        quint32 sampleSize = static_cast<quint32>(format->sampleSize() / 8.0);

        QByteArray data(waveCount * samplesPerWave * sampleSize * format->channelCount(), '[=13=]');

        unsigned char* dataPointer = reinterpret_cast<unsigned char*>(data.data());

        for (quint32 currentWave = 0; currentWave < waveCount; currentWave++)
        {
            for (int currentSample = 0; currentSample < samplesPerWave; currentSample++)
            {
                double nextRadStep = (currentSample / static_cast<double>(samplesPerWave)) * (2 * M_PI);

                quint16 sampleValue = static_cast<quint16>((qSin(nextRadStep) > 0 ? 1 : -1) * 32767);

                for (int channel = 0; channel < format->channelCount(); channel++)
                {
                    qToLittleEndian(sampleValue, dataPointer);
                    dataPointer += sampleSize;
                }
            }
        }

        soundBuffer->append(data); // <-- STD::Bad_alloc Not enough memory on heap for appending all the frequencies at once in buffer 
        output->start(outputBuffer);
        qDebug() << data.size();
    }
}

void MainWindow::on_pushButton_Run_clicked()
{
    appendSound();
}

void MainWindow::on_pushButton_Stop_clicked()
{
    output->stop();
    soundBuffer->clear();
    output->reset();
    qDebug() << "Playback Stopped";
}

我想出了一个解决方案,使用 QThread 和解释器让该线程在继续本系列的下一个序列之前等待更多事件。

通过这样做,我不需要将完整的 3~4 小时 PCM 数据同时加载到 QBufferArray 中,而是将其分解。 运行 一个较小的序列,然后等待线程完成,然后依次加载下一个序列,依此类推,直到所有序列都播放完毕。

不再 std::bad_alloc 因为线程在任何给定时间只使用堆上的大约 80mb。