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

时间:2018-08-14 10:46:31

标签: c++ qt audio wav

使用QAudioOut,我试图按顺序播放存储在QByteArray中的数据...当添加的数据很少时,此方法有效,但是当数据过多时,可以说如果将2到3小时的RAW PCM从不同的组合追加到QByteArray一次,则由于堆的大小不足以同时容纳所有数据,因此将一次生成std::bad_alloc

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

下面是一个转换后的函数,它采用列表中的值 第一个440Hz,持续1800000毫秒,并创建RAW PCM方波。然后起作用的作品将其附加到QByteArray上,然后播放。

如果没有来自多个已添加序列的大量附加数据,这将起作用。

我正在寻找一种方法,可以从列表中一次执行一个操作,然后创建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(), '\0');

    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(), '\0');

        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";
}

1 个答案:

答案 0 :(得分:0)

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

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

不再需要std :: bad_alloc了,因为线程在任何给定时间仅在堆上使用约80mb。