Reputation: 76
Using QAudioOut
Im trying to play data that is stored in a QByteArray
in a sequence... this works when there is little data being appended, however when the data gets too much, lets say a 2~3 hour RAW PCM appending from different combinations this data to a QByteArray
all at once will result in a std::bad_alloc
due to the heap that's not big enough to hold all the data at the same time.
I know where the problem occurs and I think I have a possible solution, its just I have no idea on how to go about implementing it.
Below is a converted function that takes the values in the list
first one 440Hz for 1800000 msec and created RAW PCM square wave. Which works then appends it to a QByteArray
then plays it.
This will work if there isn't a lot of appended data form multiple added sequences.
Im looking for a way to do one at a time from the list then create the wave, play that one for x milliseconds then move on to the next entry in the MySeq list. The list can contain large sequences of 3 minute frequencies that runs for hours.
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()
}
I wish to fill the QByteArray
with only one sequence at a time then Play it with QAudioOutput
then clear the ByteArray
then load the next sequence repeat until all sequences are done in the QStringList
.
The problem with this approach now is that QAudioOutput
is asynchronous and doesn't wait for the first sequence to finish
If I loop over the list as demonstrated above they load one after the other with only the last frequency actually playing. its like the loop keep overwriting the previous sequence.
Im not sure if QEventLoop
(Something I haven't used yet) is needed here or Threading. I have tried a couple of different approaches with no success. Any advice would be greatly appreciated. This is a continuation of the following question I had about wave files and data and frequency generations located here
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";
}
Upvotes: 0
Views: 239
Reputation: 76
I came up with a solution using QThread and a Interpreter for that thread to wait for more events before continuing the next sequence in the series.
By doing this I don't need to load the full 3~4 hour PCM data into a QBufferArray all at the same time but rather break it up. Run a smaller sequence then wait on the thread to finish then load the next sequence in line and so on until all of them are done playing.
No more std::bad_alloc since the thread only uses around 80mb on the heap at any given time.
Upvotes: 0