Qt audio file to wave like audacity - c++
I have to manage movie and audio file and I need to render a wave of the sound like in audacity. But I just find example for realtime render.
I want to render all the file without playing it.
Expected result:
My actual result:
With Qt I tried to use QAudioDecoder to open my file and get a QAudioBuffer but I don't find an algorithm to transform all the data into wave. I also try to see with the Qt Spectrum Example but it's not trivial to understand and it's still in realtime.
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include <QWidget>
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QWidget
{
Q_OBJECT
public:
Track(QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
protected:
void paintEvent(QPaintEvent *e) override;
private:
int pointDistance(const QPoint& a, const QPoint& b);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QByteArray byteArr;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QPaintEvent>
#include <QPainter>
#include <QAudioDecoder>
Track::Track(QWidget *parent)
: QWidget(parent)
, decoder(new QAudioDecoder(this))
{
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(update()));
}
Track::~Track()
{
delete decoder;
}
void Track::setSource(const QString &fileName)
{
byteArr.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
byteArr.append(buffer.constData<char>(), buffer.byteCount());
}
void Track::paintEvent(QPaintEvent *e)
{
QWidget::paintEvent(e);
int w = width(), h = height();
QBrush backgroundBrush(Qt::white);
QPainter painter(this);
painter.fillRect(0, 0, w, h, backgroundBrush);
painter.drawLine(0, h/2, w, h/2);
if (!byteArr.isEmpty()){
QPen pen(QColor(Qt::blue));
painter.setPen(pen);
int length = byteArr.size();
int samplesPerPixel = length/w;
int idx=0;
for (int i=0; i<w; i++){
QLine line;
int higher = 0;
for (int j=0; j<samplesPerPixel && idx+1<length; j++){
const QPoint a(i, byteArr.at(idx)+(h/2));
const QPoint b(i, byteArr.at(idx+1)+(h/2));
if (higher < pointDistance(a, b))
line = QLine(a, b);
idx++;
}
painter.drawLine(line);
}
}
}
int Track::pointDistance(const QPoint &a, const QPoint &b)
{
int ret = 0;
ret = sqrt(pow(b.x()-a.x(), 2) + pow(b.y()-a.y(), 2));
return ret;
}
I finally found a solution by using QCustomPlot widget (by reading this post):
My result:
My track.h:
#ifndef TRACK_H
#define TRACK_H
#include "qcustomplot.h"
#include <QAudioBuffer>
class QAudioDecoder;
class Track : public QCustomPlot
{
Q_OBJECT
public:
Track(TrackType type, QWidget *parent = Q_NULLPTR);
~Track();
void setSource(const QString &fileName);
public slots:
void setBuffer();
void plot();
private:
qreal getPeakValue(const QAudioFormat& format);
QAudioDecoder *decoder;
QAudioBuffer buffer;
QVector<double> samples;
QCPGraph *wavePlot;
};
#endif // TRACK_H
My track.cpp:
#include "track.h"
#include <QAudioDecoder>
Track::Track(Track::TrackType type, QWidget *parent)
: QCustomPlot(parent)
, decoder(new QAudioDecoder(this))
{
this->type = type;
wavePlot = addGraph();
setMinimumHeight(50);
connect(decoder, SIGNAL(bufferReady()), this, SLOT(setBuffer()));
connect(decoder, SIGNAL(finished()), this, SLOT(plot()));
}
Track::~Track()
{
delete decoder;
// wavePlot delete auto ?
}
void Track::setSource(const QString &fileName)
{
samples.clear();
decoder->setSourceFilename(fileName);
decoder->start();
}
void Track::setBuffer()
{
buffer = decoder->read();
qreal peak = getPeakValue(buffer.format());
const qint16 *data = buffer.constData<qint16>();
int count = buffer.sampleCount() / 2;
for (int i=0; i<count; i++){
double val = data[i]/peak;
samples.append(val);
}
}
void Track::plot()
{
QVector<double> x(samples.size());
for (int i=0; i<x.size(); i++)
x[i] = i;
wavePlot->addData(x, samples);
yAxis->setRange(QCPRange(-1, 1));
xAxis->setRange(QCPRange(0, samples.size()));
replot();
}
/**
* https://stackoverflow.com/questions/46947668/draw-waveform-from-raw-data-using-qaudioprobe
* #brief Track::getPeakValue
* #param format
* #return The peak value
*/
qreal Track::getPeakValue(const QAudioFormat &format)
{
qreal ret(0);
if (format.isValid()){
switch (format.sampleType()) {
case QAudioFormat::Unknown:
break;
case QAudioFormat::Float:
if (format.sampleSize() != 32) // other sample formats are not supported
ret = 0;
else
ret = 1.00003;
break;
case QAudioFormat::SignedInt:
if (format.sampleSize() == 32)
#ifdef Q_OS_WIN
ret = INT_MAX;
#endif
#ifdef Q_OS_UNIX
ret = SHRT_MAX;
#endif
else if (format.sampleSize() == 16)
ret = SHRT_MAX;
else if (format.sampleSize() == 8)
ret = CHAR_MAX;
break;
case QAudioFormat::UnSignedInt:
if (format.sampleSize() == 32)
ret = UINT_MAX;
else if (format.sampleSize() == 16)
ret = USHRT_MAX;
else if (format.sampleSize() == 8)
ret = UCHAR_MAX;
break;
default:
break;
}
}
return ret;
}
Related
QT real time graph has flickering problem
i am using openglseris (line) to display incoming data. i edited the opengl series example given by the qt as per my needs. How can I update the openglseris without flickering the display (smooth data update)? Because so far I have managed to display incoming data but the display is flickering every time it updates the value. //***********datasource.h**********// #ifndef DATASOURCE_H #define DATASOURCE_H #include <QtCore/QObject> #include <QtCharts/QXYSeries> #include <QtWidgets/QLabel> #include <QtCore/QElapsedTimer> #include <QtCore/QTimer> QT_CHARTS_USE_NAMESPACE class DataSource : public QObject { Q_OBJECT public: explicit DataSource(QObject *parent = 0); void startUpdates(const QList<QXYSeries *> &seriesList, QLabel *fpsLabel); public slots: void generateData(int seriesCount, int rowCount, int colCount); void update(QAbstractSeries *series, int seriesIndex); void handleSceneChanged(); void updateAllSeries(); private: QVector<QVector<QVector<QPointF> > > m_data; int m_index; QList<QXYSeries *> m_seriesList; QLabel *m_fpsLabel; QElapsedTimer m_fpsTimer; QTimer m_dataUpdater; }; #endif // DATASOURCE_H //*************datasource.cpp*******************// #include "datasource.h" #include <QtCore/QtMath> #include<QDebug> QT_CHARTS_USE_NAMESPACE int shift=0; int c[2048]={50,149,143,148,151,151,150,150,149,149,152,148,148,147,146,152,149,150,147,151,150,149,148,149,152,150,148,150,147,149,149,149,149,151,152,151,149,148,151,147,151,147,150,147,151,150,154,150,151,151,151,151,154,154,150,152,148,151,150,148,150,151,149,151,150,156,155,159,158,154,154,151,152,154,155,150,154,149,150,149,154,152,155,150,154,152,155,150,149,154,150,156,154,150,151,150,151,155,151,157,151,152,150,151,148,150,150,157,150,156,150,147,155,150,155,155,152,150,154,151,152,151,150,152,151,157,151,152,150,149,154,152,154,154,151,151,150,150,157,154,154,155,150,155,157,150,152,151,156,151,150,152,152,154,155,150,155,150,150,156,154,149,152,154,154,154,151,150,156,148,150,157,150,154,150,154,154,154,156,150,150,155,156,155,156,154,154,150,154,155,158,151,154,154,150,150,156,151,148,150,151,150,154,150,156,150,154,151,154,150,150,157,154,155,154,157,155,156,152,155,155,157,154,159,156,156,154,155,154,156,156,151,152,150,150,156,154,152,159,150,150,158,150,155,156,155,156,155,155,154,154,154,155,155,155,154,149,156,150,156,150,155,155,156,157,150,157,157,151,157,157,158,152,150,156,155,154,154,155,156,154,154,156,150,157,154,150,157,157,152,155,155,154,154,155,154,156,156,150,155,154,157,155,156,156,150,155,157,154,157,154,161,155,155,155,158,156,156,150,154,155,155,157,157,157,152,155,156,159,155,154,154,155,157,151,159,154,157,154,157,158,154,155,155,155,159,155,156,156,157,154,158,155,160,157,157,158,158,155,158,155,155,155,150,159,156,157,158,156,157,156,154,156,158,157,156,159,156,157,158,155,157,154,157,155,157,156,157,157,159,156,157,156,162,158,155,159,156,156,159,155,157,155,158,155,162,156,157,154,163,157,158,156,161,157,156,157,156,154,158,159,160,156,159,159,157,159,155,162,159,156,155,157,154,155,156,157,156,157,155,159,157,160,150,157,158,152,157,155,154,158,155,158,158,158,155,158,156,156,157,159,156,159,156,157,157,156,156,158,155,155,160,150,158,154,159,157,154,157,155,157,159,154,157,150,155,155,155,152,155,159,158,155,155,158,156,154,154,155,157,157,154,158,155,159,155,160,156,159,157,155,150,160,156,150,154,157,150,158,155,150,154,157,155,158,160,158,154,159,157,157,157,155,156,159,154,157,160,154,155,157,156,150,156,157,156,158,150,156,156,154,157,156,159,158,158,156,155,156,155,156,155,160,155,156,158,157,154,150,158,157,155,157,155,158,156,159,156,157,159,157,156,157,159,154,160,158,158,160,156,155,154,161,159,160,157,156,154,155,154,156,159,158,155,154,150,158,162,156,156,155,154,160,159,162,160,159,158,161,150,159,156,155,160,158,159,158,155,161,155,157,154,154,156,150,156,163,154,149,160,160,155,159,155,156,155,150,157,158,157,157,154,157,155,158,155,156,156,156,155,150,156,157,155,155,155,157,157,157,157,155,157,157,155,159,158,150,156,157,155,158,156,158,155,154,157,157,156,155,156,155,160,157,159,150,158,155,162,157,159,159,155,156,154,155,158,157,161,156,158,157,155,156,158,158,157,156,158,157,154,155,150,158,156,160,160,160,156,157,154,155,150,159,156,152,156,160,158,157,154,158,158,158,155,156,160,160,155,156,157,158,157,159,157,161,155,152,157,158,154,157,160,156,158,155,158,158,156,155,155,150,154,156,155,158,155,155,157,156,158,154,155,158,162,156,159,158,157,159,154,155,160,157,158,154,155,157,158,159,158,158,157,155,156,159,159,155,155,158,155,155,158,155,160,161,157,159,159,156,155,150,156,155,150,156,157,155,155,158,155,156,159,156,158,162,155,160,154,158,158,158,157,160,156,155,157,152,156,160,158,160,155,158,155,155,157,155,156,159,154,155,157,157,160,159,156,157,162,158,156,157,154,160,157,156,159,159,156,155,159,158,157,156,162,160,158,157,162,157,158,157,157,163,156,156,160,161,154,155,156,159,157,156,158,159,157,155,157,162,157,160,158,159,155,156,155,160,158,157,155,160,159,156,157,157,160,155,157,158,157,157,156,160,155,157,156,157,159,159,160,159,160,157,162,157,157,154,157,157,159,161,155,160,155,155,157,160,156,158,158,161,159,156,156,158,160,160,158,155,157,161,154,160,156,155,156,160,158,156,157,150,155,154,160,157,154,155,155,160,155,154,158,157,155,160,155,156,160,154,157,154,158,157,159,157,155,152,155,156,156,155,155,155,155,156,156,159,159,157,160,155,158,158,157,157,150,155,156,155,156,156,155,159,150,161,155,159,156,156,158,161,155,157,156,159,155,150,155,156,161,155,156,156,154,157,155,157,158,156,158,160,155,161,158,156,159,157,156,159,155,158,162,155,160,158,161,160,156,158,164,156,158,159,158,154,161,158,157,157,156,156,160,157,155,156,160,160,155,158,156,162,160,160,158,157,160,163,156,154,154,162,156,157,155,160,150,158,155,156,154,157,159,160,158,157,162,154,158,163,158,154,158,154,156,155,158,156,159,158,156,156,159,154,159,154,159,156,157,157,160,159,159,156,150,159,158,156,156,156,158,155,158,156,155,156,157,158,156,156,159,157,157,156,156,159,157,160,159,161,159,158,158,160,157,158,156,156,160,160,158,157,157,158,162,150,156,158,158,163,159,158,154,156,159,159,159,157,158,155,161,155,159,155,160,157,156,161,158,157,156,157,158,155,160,158,160,156,157,159,160,156,156,158,156,156,156,155,157,156,156,160,157,154,159,160,157,158,162,159,158,161,158,156,160,156,159,162,162,157,158,160,159,158,160,160,157,156,159,158,160,157,161,158,158,154,160,160,161,154,161,159,156,158,157,160,159,161,157,157,162,156,157,159,152,161,157,158,158,157,158,157,158,158,157,159,159,159,150,157,159,158,151,158,156,160,158,158,157,156,158,158,156,156,158,160,155,159,156,161,156,163,154,161,159,157,159,161,158,157,155,159,156,158,155,161,158,156,161,155,159,158,159,155,162,154,162,161,157,159,159,156,160,157,159,160,160,159,156,156,156,157,157,163,159,161,156,158,156,159,156,157,158,156,160,163,163,156,160,159,157,156,160,160,155,157,155,160,157,160,160,157,158,159,157,159,160,161,160,159,156,158,157,157,156,160,156,162,159,157,154,156,155,162,159,159,161,157,162,159,156,156,156,162,158,161,158,167,159,159,156,158,156,159,157,154,159,160,159,155,163,158,158,162,156,162,158,158,156,161,159,155,160,155,159,150,161,155,156,157,157,155,157,155,163,155,158,157,155,157,161,154,154,155,156,157,159,156,156,159,158,158,158,161,159,156,158,158,158,160,160,162,158,159,156,161,154,154,158,157,161,159,157,159,154,159,161,159,159,160,159,162,160,158,158,157,160,159,161,161,157,158,157,158,162,160,161,159,161,159,162,159,157,161,160,158,160,160,156,157,157,159,159,159,161,158,160,155,157,162,159,162,161,159,161,158,159,162,155,159,161,155,160,157,156,157,160,154,157,159,157,158,160,159,160,157,156,156,158,157,158,154,161,159,154,158,159,160,160,159,162,159,161,158,161,158,156,160,160,158,156,159,158,155,155,156,160,159,162,158,160,159,159,159,156,156,156,164,155,158,157,160,157,160,158,160,158,158,159,152,159,158,157,162,160,160,160,159,155,158,154,159,161,157,160,158,157,154,160,155,158,160,152,156,150,158,157,159,158,162,158,157,157,156,160,159,158,159,155,156,160,154,158,155,156,152,156,156,156,156,154,158,156,157,155,154,156,156,157,156,158,157,156,159,161,152,156,155,152,156,156,155,158,159,157,156,157,156,158,158,155,152,161,159,155,156,157,152,156,158,158,150,155,152,158,159,158,155,160,150,154,156,158,155,157,160,156,157,155,155,157,159,158,156,158,158,156,159,156,157,150,155,158,158,157,158,159,158,150,160,156,157,158,155,154,157,156,156,160,157,161,157,158,158,158,150,150,154,150,150,150,157,157,150,158,155,159,158,158,150,158,159,154,154,157,154,155,157,155,154,155,157,154,157,156,157,158,160,155,159,155,157,160,150,152,156,156,156,154,157,156,154,156,159,154,155,150,150,158,158,156,160,158,152,150,157,154,159,150,150,158,155,154,157,155,154,150,152,156,156,154,159,152,156,156,150,156,156,157,154,152,154,151,155,155,152,158,157,161,152,152,157,154,158,150,156,158,150,157,154,156,154,155,158,156,154,150,154,158,150,159,155,151,152,155,154,154,154,154,152,150,154,154,154,152,150,150,156,150,157,150,151,151,151,155,150,150,158,150,152,155,155,152,150,156,151,152,150,151,158,150,152,152,158,148,154,151,152,154,155,150,154,154,154,155,155,150,150,156,151,155,151,151,151,152,152,150,151,151,151,151,150,156,152,159,148,156,154,150,149,156,152,154,155,150,152,155,151,155,149,150,150,151,155,149,155,150,152,152,149,150,154,151,149,156,154,149,154,151,147,149,150,149,151,154,149,155,151,149,148,150,155,150,149,149,152,148,154,150,149,150,149,151,154,149,152,154,150,150,151,152,155,150,150,151,149,148,152,149}; int b[2048]={150,149,143,148,151,151,150,150,149,149,152,148,148,147,146,152,149,150,147,151,150,149,148,149,152,150,148,150,147,149,149,149,149,151,152,151,149,148,151,147,151,147,150,147,151,153,154,153,151,151,151,151,154,154,153,152,148,151,150,148,153,151,149,151,150,156,155,159,158,154,154,151,152,154,155,150,154,149,153,149,154,152,155,150,154,152,155,150,149,154,150,156,154,153,151,153,151,155,151,157,151,152,153,151,148,153,150,157,150,156,153,147,155,150,155,155,152,153,154,151,152,151,153,152,151,157,151,152,153,149,154,152,154,154,151,151,153,153,157,154,154,155,153,155,157,153,152,151,156,151,153,152,152,154,155,153,155,150,150,156,154,149,152,154,154,154,151,150,156,148,150,157,153,154,153,154,154,154,156,150,150,155,156,155,156,154,154,153,154,155,158,151,154,154,153,153,156,151,148,153,151,153,154,153,156,153,154,151,154,153,153,157,154,155,154,157,155,156,152,155,155,157,154,159,156,156,154,155,154,156,156,151,152,153,153,156,154,152,159,150,153,158,153,155,156,155,156,155,155,154,154,154,155,155,155,154,149,156,153,156,153,155,155,156,157,150,157,157,151,157,157,158,152,153,156,155,154,154,155,156,154,154,156,153,157,154,150,157,157,152,155,155,154,154,155,154,156,156,153,155,154,157,155,156,156,153,155,157,154,157,154,161,155,155,155,158,156,156,153,154,155,155,157,157,157,152,155,156,159,155,154,154,155,157,151,159,154,157,154,157,158,154,155,155,155,159,155,156,156,157,154,158,155,160,157,157,158,158,155,158,155,155,155,153,159,156,157,158,156,157,156,154,156,158,157,156,159,156,157,158,155,157,154,157,155,157,156,157,157,159,156,157,156,162,158,155,159,156,156,159,155,157,155,158,155,162,156,157,154,163,157,158,156,161,157,156,157,156,154,158,159,160,156,159,159,157,159,155,162,159,156,155,157,154,155,156,157,156,157,155,159,157,160,153,157,158,152,157,155,154,158,155,158,158,158,155,158,156,156,157,159,156,159,156,157,157,156,156,158,155,155,160,153,158,154,159,157,154,157,155,157,159,154,157,153,155,155,155,152,155,159,158,155,155,158,156,154,154,155,157,157,154,158,155,159,155,160,156,159,157,155,153,160,156,150,154,157,153,158,155,153,154,157,155,158,160,158,154,159,157,157,157,155,156,159,154,157,160,154,155,157,156,153,156,157,156,158,153,156,156,154,157,156,159,158,158,156,155,156,155,156,155,160,155,156,158,157,154,153,158,157,155,157,155,158,156,159,156,157,159,157,156,157,159,154,160,158,158,160,156,155,154,161,159,160,157,156,154,155,154,156,159,158,155,154,153,158,162,156,156,155,154,160,159,162,160,159,158,161,153,159,156,155,160,158,159,158,155,161,155,157,154,154,156,153,156,163,154,149,160,160,155,159,155,156,155,153,157,158,157,157,154,157,155,158,155,156,156,156,155,153,156,157,155,155,155,157,157,157,157,155,157,157,155,159,158,153,156,157,155,158,156,158,155,154,157,157,156,155,156,155,160,157,159,153,158,155,162,157,159,159,155,156,154,155,158,157,161,156,158,157,155,156,158,158,157,156,158,157,154,155,153,158,156,160,160,160,156,157,154,155,153,159,156,152,156,160,158,157,154,158,158,158,155,156,160,160,155,156,157,158,157,159,157,161,155,152,157,158,154,157,160,156,158,155,158,158,156,155,155,153,154,156,155,158,155,155,157,156,158,154,155,158,162,156,159,158,157,159,154,155,160,157,158,154,155,157,158,159,158,158,157,155,156,159,159,155,155,158,155,155,158,155,160,161,157,159,159,156,155,153,156,155,153,156,157,155,155,158,155,156,159,156,158,162,155,160,154,158,158,158,157,160,156,155,157,152,156,160,158,160,155,158,155,155,157,155,156,159,154,155,157,157,160,159,156,157,162,158,156,157,154,160,157,156,159,159,156,155,159,158,157,156,162,160,158,157,162,157,158,157,157,163,156,156,160,161,154,155,156,159,157,156,158,159,157,155,157,162,157,160,158,159,155,156,155,160,158,157,155,160,159,156,157,157,160,155,157,158,157,157,156,160,155,157,156,157,159,159,160,159,160,157,162,157,157,154,157,157,159,161,155,160,155,155,157,160,156,158,158,161,159,156,156,158,160,160,158,155,157,161,154,160,156,155,156,160,158,156,157,153,155,154,160,157,154,155,155,160,155,154,158,157,155,160,155,156,160,154,157,154,158,157,159,157,155,152,155,156,156,155,155,155,155,156,156,159,159,157,160,155,158,158,157,157,153,155,156,155,156,156,155,159,153,161,155,159,156,156,158,161,155,157,156,159,155,153,155,156,161,155,156,156,154,157,155,157,158,156,158,160,155,161,158,156,159,157,156,159,155,158,162,155,160,158,161,160,156,158,164,156,158,159,158,154,161,158,157,157,156,156,160,157,155,156,160,160,155,158,156,162,160,160,158,157,160,163,156,154,154,162,156,157,155,160,153,158,155,156,154,157,159,160,158,157,162,154,158,163,158,154,158,154,156,155,158,156,159,158,156,156,159,154,159,154,159,156,157,157,160,159,159,156,153,159,158,156,156,156,158,155,158,156,155,156,157,158,156,156,159,157,157,156,156,159,157,160,159,161,159,158,158,160,157,158,156,156,160,160,158,157,157,158,162,153,156,158,158,163,159,158,154,156,159,159,159,157,158,155,161,155,159,155,160,157,156,161,158,157,156,157,158,155,160,158,160,156,157,159,160,156,156,158,156,156,156,155,157,156,156,160,157,154,159,160,157,158,162,159,158,161,158,156,160,156,159,162,162,157,158,160,159,158,160,160,157,156,159,158,160,157,161,158,158,154,160,160,161,154,161,159,156,158,157,160,159,161,157,157,162,156,157,159,152,161,157,158,158,157,158,157,158,158,157,159,159,159,153,157,159,158,151,158,156,160,158,158,157,156,158,158,156,156,158,160,155,159,156,161,156,163,154,161,159,157,159,161,158,157,155,159,156,158,155,161,158,156,161,155,159,158,159,155,162,154,162,161,157,159,159,156,160,157,159,160,160,159,156,156,156,157,157,163,159,161,156,158,156,159,156,157,158,156,160,163,163,156,160,159,157,156,160,160,155,157,155,160,157,160,160,157,158,159,157,159,160,161,160,159,156,158,157,157,156,160,156,162,159,157,154,156,155,162,159,159,161,157,162,159,156,156,156,162,158,161,158,167,159,159,156,158,156,159,157,154,159,160,159,155,163,158,158,162,156,162,158,158,156,161,159,155,160,155,159,153,161,155,156,157,157,155,157,155,163,155,158,157,155,157,161,154,154,155,156,157,159,156,156,159,158,158,158,161,159,156,158,158,158,160,160,162,158,159,156,161,154,154,158,157,161,159,157,159,154,159,161,159,159,160,159,162,160,158,158,157,160,159,161,161,157,158,157,158,162,160,161,159,161,159,162,159,157,161,160,158,160,160,156,157,157,159,159,159,161,158,160,155,157,162,159,162,161,159,161,158,159,162,155,159,161,155,160,157,156,157,160,154,157,159,157,158,160,159,160,157,156,156,158,157,158,154,161,159,154,158,159,160,160,159,162,159,161,158,161,158,156,160,160,158,156,159,158,155,155,156,160,159,162,158,160,159,159,159,156,156,156,164,155,158,157,160,157,160,158,160,158,158,159,152,159,158,157,162,160,160,160,159,155,158,154,159,161,157,160,158,157,154,160,155,158,160,152,156,153,158,157,159,158,162,158,157,157,156,160,159,158,159,155,156,160,154,158,155,156,152,156,156,156,156,154,158,156,157,155,154,156,156,157,156,158,157,156,159,161,152,156,155,152,156,156,155,158,159,157,156,157,156,158,158,155,152,161,159,155,156,157,152,156,158,158,150,155,152,158,159,158,155,160,150,154,156,158,155,157,160,156,157,155,155,157,159,158,156,158,158,156,159,156,157,153,155,158,158,157,158,159,158,153,160,156,157,158,155,154,157,156,156,160,157,161,157,158,158,158,153,153,154,153,153,153,157,157,150,158,155,159,158,158,153,158,159,154,154,157,154,155,157,155,154,155,157,154,157,156,157,158,160,155,159,155,157,160,153,152,156,156,156,154,157,156,154,156,159,154,155,153,153,158,158,156,160,158,152,153,157,154,159,153,153,158,155,154,157,155,154,153,152,156,156,154,159,152,156,156,153,156,156,157,154,152,154,151,155,155,152,158,157,161,152,152,157,154,158,153,156,158,153,157,154,156,154,155,158,156,154,153,154,158,153,159,155,151,152,155,154,154,154,154,152,153,154,154,154,152,153,153,156,153,157,153,151,151,151,155,153,153,158,153,152,155,155,152,153,156,151,152,153,151,158,153,152,152,158,148,154,151,152,154,155,153,154,154,154,155,155,153,153,156,151,155,151,151,151,152,152,150,151,151,151,151,150,156,152,159,148,156,154,150,149,156,152,154,155,153,152,155,151,155,149,153,150,151,155,149,155,153,152,152,149,153,154,151,149,156,154,149,154,151,147,149,153,149,151,154,149,155,151,149,148,150,155,150,149,149,152,148,154,153,149,150,149,151,154,149,152,154,153,150,151,152,155,153,150,151,149,148,152,149 }; DataSource::DataSource(QObject *parent) : QObject(parent), m_index(-1) { generateData(0, 0, 0); } void DataSource::update(QAbstractSeries *series, int seriesIndex) { if (series) { QXYSeries *xySeries = static_cast<QXYSeries *>(series); const QVector<QVector<QPointF> > &seriesData = m_data.at(seriesIndex); if (seriesIndex == 0) m_index++; if (m_index > seriesData.count() - 1) m_index = 0; QVector<QPointF> points = seriesData.at(m_index); // Use replace instead of clear + append, it's optimized for performance xySeries->replace(points); } } void DataSource::handleSceneChanged() { m_dataUpdater.start(); } void DataSource::updateAllSeries() { static int frameCount = 0; static QString labelText = QStringLiteral("FPS: %1"); for (int i = 0; i < m_seriesList.size(); i++) update(m_seriesList[i], i); frameCount++; int elapsed = m_fpsTimer.elapsed(); if (elapsed >= 1000) { elapsed = m_fpsTimer.restart(); qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed)))); m_fpsLabel->setText(labelText.arg(QString::number(fps, 'f', 1))); m_fpsLabel->adjustSize(); frameCount = 0; } m_data.clear(); // Append the new data depending on the type QVector<QVector<QPointF> > seriesData; QVector<QPointF> points; // points.reserve(2048); for (int j(0); j < 2048; j++) { qreal x(0); qreal y(0); if(shift==0) { y = b[j] ; x=j; }else { y = c[j] ; x=j; } points.append(QPointF(x, y)); } seriesData.append(points); m_data.append(seriesData); if(shift==0) { shift=1; }else { shift=0; } } void DataSource::startUpdates(const QList<QXYSeries *> &seriesList, QLabel *fpsLabel) { m_seriesList = seriesList; m_fpsLabel = fpsLabel; m_dataUpdater.setInterval(500); m_dataUpdater.setSingleShot(true); QObject::connect(&m_dataUpdater, &QTimer::timeout,this, &DataSource::updateAllSeries); m_fpsTimer.start(); updateAllSeries(); } void DataSource::generateData(int seriesCount, int rowCount, int colCount) { // Remove previous data foreach (QVector<QVector<QPointF> > seriesData, m_data) { foreach (QVector<QPointF> row, seriesData) row.clear(); } m_data.clear(); // Append the new data depending on the type for (int k(0); k < seriesCount; k++) { QVector<QVector<QPointF> > seriesData; for (int i(0); i < rowCount; i++) { QVector<QPointF> points; points.reserve(colCount); for (int j(0); j < colCount; j++) { qreal x(0); qreal y(0); if(shift==0) { y = a[j] ; x=j; }else { y = b[j] ; x=j; } points.append(QPointF(x, y)); } seriesData.append(points); } m_data.append(seriesData); } if(shift==0) { shift=1; }else { shift=0; } qDebug()<<"test"; } //***********main.cpp**********// #include "datasource.h" #include <QtWidgets/QApplication> #include <QtWidgets/QMainWindow> #include <QtCharts/QChartView> #include <QtCharts/QLineSeries> #include <QtCharts/QScatterSeries> #include <QtCharts/QValueAxis> #include <QtCharts/QLogValueAxis> #include <QtWidgets/QLabel> QT_CHARTS_USE_NAMESPACE int main(int argc, char *argv[]) { QApplication a(argc, argv); QStringList colors; colors << "red" << "blue" << "green" << "black"; QChart *chart = new QChart(); chart->legend()->hide(); QValueAxis *axisX = new QValueAxis; QValueAxis *axisY = new QValueAxis; chart->addAxis(axisX, Qt::AlignBottom); chart->addAxis(axisY, Qt::AlignLeft); const int seriesCount = 1; const int pointCount = 2048; chart->setTitle("OpenGL Accelerated Series"); QList<QXYSeries *> seriesList; for (int i = 0; i < seriesCount; i++) { QXYSeries *series = 0; int colorIndex = i % colors.size(); if (i % 2) { series = new QScatterSeries; QScatterSeries *scatter = static_cast<QScatterSeries *>(series); scatter->setColor(QColor(colors.at(colorIndex))); scatter->setMarkerSize(qreal(colorIndex + 2) / 2.0); // Scatter pen doesn't have affect in OpenGL drawing, but if you disable OpenGL drawing // this makes the marker border visible and gives comparable marker size to OpenGL // scatter points. scatter->setPen(QPen("black")); } else { series = new QLineSeries; series->setPen(QPen(QBrush(QColor(colors.at(colorIndex))), qreal(colorIndex + 2) / 2.0)); } seriesList.append(series); //![1] series->setUseOpenGL(true); //![1] chart->addSeries(series); series->attachAxis(axisX); series->attachAxis(axisY); } if (axisX->type() == QAbstractAxis::AxisTypeLogValue) axisX->setRange(0.1, 2048.0); else axisX->setRange(0, 2048.0); if (axisY->type() == QAbstractAxis::AxisTypeLogValue) axisY->setRange(0.1, 255.0); else axisY->setRange(0, 255.0); QChartView *chartView = new QChartView(chart); QMainWindow window; window.setCentralWidget(chartView); window.resize(600, 400); window.show(); DataSource dataSource; dataSource.generateData(seriesCount, 1, pointCount); QLabel *fpsLabel = new QLabel(&window); QLabel *countLabel = new QLabel(&window); QString countText = QStringLiteral("Total point count: %1"); countLabel->setText(countText.arg(pointCount * seriesCount)); countLabel->adjustSize(); fpsLabel->move(10, 2); fpsLabel->adjustSize(); fpsLabel->raise(); fpsLabel->show(); countLabel->move(10, fpsLabel->height()); fpsLabel->raise(); countLabel->show(); // We can get more than one changed event per frame, so do async update. // This also allows the application to be responsive. QObject::connect(chart->scene(), &QGraphicsScene::changed, &dataSource, &DataSource::handleSceneChanged); dataSource.startUpdates(seriesList, fpsLabel); return a.exec(); }
Qt C++ Drag QHeaderView between tables
I want to copy the selected column of a QTableWidget to another one. So I tried to make selected columns draggable by adding this code: void makeDraggable(QTableWidget *table) { table->setDragEnabled(true); table->setAcceptDrops(true); table->setSelectionBehavior(QAbstractItemView::SelectColumns); } Result I got: But I want to drag a whole column (horizontal and vertical headers) by clicking on headers only, not on cells, and copy its data to another table including the header text.
Dragging between different tables inside one application can be done with reimplementing custom QHeaderView and QTableWidget. In my example I generate text with indecies of table and column for drag event. Custom header: #include <QHeaderView> class ITableManager; class DraggableHeaderView : public QHeaderView { Q_OBJECT public: explicit DraggableHeaderView(Qt::Orientation orientation, QWidget *parent = 0); int tag() const; void setTag(const int tag); void setTableManager(ITableManager* manager); protected: void mouseMoveEvent(QMouseEvent *e); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); signals: public slots: private: int m_tag; //internal index of table ITableManager *m_tableManager; //manager will convert table index into pointer }; Custom header cpp #include <QMouseEvent> #include <QDrag> #include <QMimeData> #include <QDebug> #include <QTableWidget> #include <ITableManager.h> DraggableHeaderView::DraggableHeaderView(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent) { m_tag = 0; m_tableManager = 0; setAcceptDrops(true); } void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e) { if (e->buttons() & Qt::LeftButton) { int index = logicalIndexAt(e->pos()); QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; //custom drag text with indecies inside QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) + ";Index:" + QString::number(index); mimeData->setText(mimeTxt); drag->setMimeData(mimeData); Qt::DropAction dropAction = drag->exec(); } } int DraggableHeaderView::tag() const { return m_tag; } void DraggableHeaderView::setTag(const int tag) { m_tag = tag; } void DraggableHeaderView::dragEnterEvent(QDragEnterEvent *event) { if (!m_tableManager) { event->ignore(); return; } QString dragText = event->mimeData()->text(); int index = dragText.indexOf("MoveHeader;"); if (index == 0) { event->accept(); } else { event->ignore(); } } void DraggableHeaderView::dropEvent(QDropEvent *event) { if (!m_tableManager) { event->ignore(); return; } QStringList dragText = event->mimeData()->text().split(';'); if (dragText.count() < 3 || dragText.at(0) != "MoveHeader") { event->ignore(); return; } int tableIndex = dragText.at(1).mid(6).toInt();//6 - length 'Table:' QTableWidget* tableSrc = m_tableManager->getTableFromIndex(tableIndex); if (!tableSrc) { event->ignore(); return; } //dst table as parent for header view QTableWidget *tableDst = qobject_cast<QTableWidget*> (this->parentWidget()); if (!tableDst) { event->ignore(); return; } //move column: modify for your needs //now moves only items text int columnIndex = logicalIndexAt(event->pos()); int srcColumnIndex = dragText.at(2).mid(6).toInt(); //6 - length of 'Index:' tableDst->insertColumn(columnIndex); for (int iRow = 0; iRow < tableDst->rowCount() && iRow < tableSrc->rowCount(); ++iRow) { if (tableSrc->item(iRow, srcColumnIndex)) { tableDst->setItem(iRow, columnIndex, new QTableWidgetItem(tableSrc->item(iRow, srcColumnIndex)->text())); } else { tableDst->setItem(iRow, columnIndex, new QTableWidgetItem()); } } tableSrc->removeColumn(srcColumnIndex); } void DraggableHeaderView::setTableManager(ITableManager *manager) { m_tableManager = manager; } Now create custom QTableWidget with DraggableHeaderView inside class CustomTableWidget : public QTableWidget { Q_OBJECT public: explicit CustomTableWidget(QWidget *parent = 0); void setTag(const int tag); void setTableManager(ITableManager* manager); }; CustomTableWidget::CustomTableWidget(QWidget *parent) : QTableWidget(parent) { DraggableHeaderView *headerView = new DraggableHeaderView(Qt::Horizontal, this); setHorizontalHeader(headerView); setAcceptDrops(true); } void CustomTableWidget::setTag(const int tag) { DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader()); if (header) { header->setTag(tag); } } void CustomTableWidget::setTableManager(ITableManager *manager) { DraggableHeaderView *header = qobject_cast<DraggableHeaderView*> (horizontalHeader()); if (header) { header->setTableManager(manager); } } For converting table index to pointer I use ITableManager class ITableManager { public: virtual QTableWidget* getTableFromIndex(const int index) = 0; }; And implement it in QMainWindow class MainWindow : public QMainWindow, ITableManager { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); QTableWidget* getTableFromIndex(const int index); } QTableWidget * MainWindow::getTableFromIndex(const int index) { switch (index) { case 1: return ui->tableWidget; case 2: return ui->tableWidget_2; default: return nullptr; } } Dont forget setup tags (indecies) and table manager for tables (in main window constructor) ui->tableWidget->setTag(1); ui->tableWidget_2->setTag(2); ui->tableWidget->setTableManager(this); ui->tableWidget_2->setTableManager(this); EDIT: If you want change custom pixmap for dragging just set QDrag::setPixmap void DraggableHeaderView::mouseMoveEvent(QMouseEvent *e) { if (e->buttons() & Qt::LeftButton) { int index = logicalIndexAt(e->pos()); QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; QString mimeTxt = "MoveHeader;Table:" + QString::number(m_tag) + ";Index:" + QString::number(index); mimeData->setText(mimeTxt); drag->setMimeData(mimeData); drag->setPixmap(pixmapForDrag(index)); Qt::DropAction dropAction = drag->exec(); } } And method for taking pixmap of column can be like this QPixmap DraggableHeaderView::pixmapForDrag(const int columnIndex) const { QTableWidget *table = qobject_cast<QTableWidget*> (this->parentWidget()); if (!table) { return QPixmap(); } //image for first 5 row int height = table->horizontalHeader()->height(); for (int iRow = 0; iRow < 5 && iRow < table->rowCount(); ++iRow) { height += table->rowHeight(iRow); } //clip maximum size if (height > 200) { height = 200; } QRect rect(table->columnViewportPosition(columnIndex) + table->verticalHeader()->width(), table->rowViewportPosition(0), table->columnWidth(columnIndex), height); QPixmap pixmap(rect.size()); table->render(&pixmap, QPoint(), QRegion(rect)); return pixmap; }
QAudioInput from array
I have a device with microphones that connects to my computer through Ethernet and it cannot be seen by Qt as an audio device, so, I get packets from it and put them to QByteArray. I need to play these packets from stream. Somewhere in the Internet I found a solution to almost the same problem, but there internal microphone was used. #include <QApplication> #include <iostream> #include <cassert> #include <QCoreApplication> #include <QAudioInput> #include <QAudioOutput> #include <QBuffer> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QBuffer rdBuff; QBuffer wrBuff; wrBuff.open(QBuffer::WriteOnly); rdBuff.open(QBuffer::ReadOnly); QObject::connect(&wrBuff, &QIODevice::bytesWritten, [&wrBuff, &rdBuff](qint64) { rdBuff.buffer().remove(0, rdBuff.pos()); // set pointer to the beginning of the unread data const auto res = rdBuff.seek(0); assert(res); // write new data rdBuff.buffer().append(wrBuff.buffer()); // remove all data that was already written wrBuff.buffer().clear(); wrBuff.seek(0); }); const auto decideAudioFormat = [](const QAudioDeviceInfo& devInfo) { QAudioFormat format; format.setSampleRate(8000); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); if (devInfo.isFormatSupported(format)) { return format; } else { std::cerr << "Raw audio format not supported by backend, cannot play audio.\n"; throw 0; } }; QAudioInput audioInput(decideAudioFormat(QAudioDeviceInfo::defaultInputDevice())); QAudioOutput audioOutput(decideAudioFormat(QAudioDeviceInfo::defaultOutputDevice())); audioInput.start(&wrBuff); audioOutput.start(&rdBuff); return app.exec(); } It works quite well, but I need to set QByteArray as QAudioInput's source. Is there any possible solution?
Not sure if i'm directly answering your question. But a possible solution is feed the output audio device manually (push mode) when new data comes. You can also use a custom (QFile inherited) class to record sound, and when sound come, feeds both the file and output audio device. Here is a example: AudioOutput.h: #ifndef AUDIOOUTPUT_H #define AUDIOOUTPUT_H #include <QtCore> #include <QtMultimedia> #define MAX_BUFFERED_TIME 10*1000 static inline int timeToSize(int ms, const QAudioFormat &format) { return ((format.channelCount() * (format.sampleSize() / 8) * format.sampleRate()) * ms / 1000); } class AudioOutput : public QObject { Q_OBJECT public: explicit AudioOutput(QObject *parent = nullptr); public slots: bool start(const QAudioDeviceInfo &devinfo, const QAudioFormat &format, int time_to_buffer); void write(const QByteArray &data); private slots: void verifyBuffer(); void preplay(); void play(); private: bool m_initialized; QAudioOutput *m_audio_output; QIODevice *m_device; QByteArray m_buffer; bool m_buffer_requested; bool m_play_called; int m_size_to_buffer; int m_time_to_buffer; int m_max_size_to_buffer; QAudioFormat m_format; }; #endif // AUDIOOUTPUT_H AudioRecorder.h: #ifndef AUDIORECORDER_H #define AUDIORECORDER_H #include <QtCore> #include <QtMultimedia> class AudioRecorder : public QFile { Q_OBJECT public: explicit AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent = nullptr); ~AudioRecorder(); using QFile::open; public slots: bool open(); qint64 write(const QByteArray &data); void close(); private: void writeHeader(); bool hasSupportedFormat(); QAudioFormat format; }; #endif // AUDIORECORDER_H AudioOutput.cpp: #include "audiooutput.h" AudioOutput::AudioOutput(QObject *parent) : QObject(parent) { m_initialized = false; m_audio_output = nullptr; m_device = nullptr; m_buffer_requested = true; m_play_called = false; m_size_to_buffer = 0; m_time_to_buffer = 0; m_max_size_to_buffer = 0; } bool AudioOutput::start(const QAudioDeviceInfo &devinfo, const QAudioFormat &format, int time_to_buffer) { if (!devinfo.isFormatSupported(format)) { qDebug() << "Format not supported by output device"; return m_initialized; } m_format = format; int internal_buffer_size; //Adjust internal buffer size if (format.sampleRate() >= 44100) internal_buffer_size = (1024 * 10) * format.channelCount(); else if (format.sampleRate() >= 24000) internal_buffer_size = (1024 * 6) * format.channelCount(); else internal_buffer_size = (1024 * 4) * format.channelCount(); //Initialize the audio output device m_audio_output = new QAudioOutput(devinfo, format, this); //Increase the buffer size to enable higher sample rates m_audio_output->setBufferSize(internal_buffer_size); m_time_to_buffer = time_to_buffer; //Compute the size in bytes to be buffered based on the current format m_size_to_buffer = timeToSize(m_time_to_buffer, m_format); //Define a highest size that the buffer are allowed to have in the given time //This value is used to discard too old buffered data m_max_size_to_buffer = m_size_to_buffer + timeToSize(MAX_BUFFERED_TIME, m_format); m_device = m_audio_output->start(); if (!m_device) { qDebug() << "Failed to open output audio device"; return m_initialized; } //Timer that helps to keep playing data while it's available on the internal buffer QTimer *timer_play = new QTimer(this); timer_play->setTimerType(Qt::PreciseTimer); connect(timer_play, &QTimer::timeout, this, &AudioOutput::preplay); timer_play->start(10); //Timer that checks for too old data in the buffer QTimer *timer_verifier = new QTimer(this); connect(timer_verifier, &QTimer::timeout, this, &AudioOutput::verifyBuffer); timer_verifier->start(qMax(m_time_to_buffer, 10)); m_initialized = true; return m_initialized; } void AudioOutput::verifyBuffer() { if (m_buffer.size() >= m_max_size_to_buffer) m_buffer.clear(); } void AudioOutput::write(const QByteArray &data) { m_buffer.append(data); preplay(); } void AudioOutput::preplay() { if (!m_initialized) return; //Verify if exists a pending call to play function //If not, call the play function async if (!m_play_called) { m_play_called = true; QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection); } } void AudioOutput::play() { //Set that last async call was triggered m_play_called = false; if (m_buffer.isEmpty()) { //If data is empty set that nothing should be played //until the buffer has at least the minimum buffered size already set m_buffer_requested = true; return; } else if (m_buffer.size() < m_size_to_buffer) { //If buffer doesn't contains enough data, //check if exists a already flag telling that the buffer comes //from a empty state and should not play anything until have the minimum data size if (m_buffer_requested) return; } else { //Buffer is ready and data can be played m_buffer_requested = false; } int readlen = m_audio_output->periodSize(); int chunks = m_audio_output->bytesFree() / readlen; //Play data while it's available in the output device while (chunks) { //Get chunk from the buffer QByteArray samples = m_buffer.mid(0, readlen); int len = samples.size(); m_buffer.remove(0, len); //Write data to the output device if (len) m_device->write(samples); //If chunk is smaller than the output chunk size, exit loop if (len != readlen) break; //Decrease the available number of chunks chunks--; } } AudioRecorder.cpp: #include "audiorecorder.h" AudioRecorder::AudioRecorder(const QString &name, const QAudioFormat &format, QObject *parent) : QFile(name, parent), format(format) { } AudioRecorder::~AudioRecorder() { if (!isOpen()) return; close(); } bool AudioRecorder::hasSupportedFormat() { return (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt) || (format.sampleSize() > 8 && format.sampleType() == QAudioFormat::SignedInt && format.byteOrder() == QAudioFormat::LittleEndian); } bool AudioRecorder::open() { if (!hasSupportedFormat()) { setErrorString("Wav PCM supports only 8-bit unsigned samples " "or 16-bit (or more) signed samples (in little endian)"); return false; } else { if (!QFile::open(ReadWrite | Truncate)) return false; writeHeader(); return true; } } qint64 AudioRecorder::write(const QByteArray &data) { return QFile::write(data); } void AudioRecorder::writeHeader() { QDataStream out(this); out.setByteOrder(QDataStream::LittleEndian); // RIFF chunk out.writeRawData("RIFF", 4); out << quint32(0); // Placeholder for the RIFF chunk size (filled by close()) out.writeRawData("WAVE", 4); // Format description chunk out.writeRawData("fmt ", 4); out << quint32(16); // "fmt " chunk size (always 16 for PCM) out << quint16(1); // data format (1 => PCM) out << quint16(format.channelCount()); out << quint32(format.sampleRate()); out << quint32(format.sampleRate() * format.channelCount() * format.sampleSize() / 8 ); // bytes per second out << quint16(format.channelCount() * format.sampleSize() / 8); // Block align out << quint16(format.sampleSize()); // Significant Bits Per Sample // Data chunk out.writeRawData("data", 4); out << quint32(0); // Placeholder for the data chunk size (filled by close()) Q_ASSERT(pos() == 44); // Must be 44 for WAV PCM } void AudioRecorder::close() { // Fill the header size placeholders quint32 fileSize = size(); QDataStream out(this); // Set the same ByteOrder like in writeHeader() out.setByteOrder(QDataStream::LittleEndian); // RIFF chunk size seek(4); out << quint32(fileSize - 8); // data chunk size seek(40); out << quint32(fileSize - 44); QFile::close(); } main.cpp: #include <QtCore> #include "audiooutput.h" #include "audiorecorder.h" #include <signal.h> QByteArray tone_generator() { //Tone generator from http://www.cplusplus.com/forum/general/129827/ const unsigned int samplerate = 8000; const unsigned short channels = 1; const double pi = M_PI; const qint16 amplitude = std::numeric_limits<qint16>::max() * 0.5; const unsigned short n_frequencies = 8; const unsigned short n_seconds_each = 1; float frequencies[n_frequencies] = {55.0, 110.0, 220.0, 440.0, 880.0, 1760.0, 3520.0, 7040.0}; const int n_samples = channels * samplerate * n_frequencies * n_seconds_each; QVector<qint16> data; data.resize(n_samples); int index = n_samples / n_frequencies; for (unsigned short i = 0; i < n_frequencies; ++i) { float freq = frequencies[i]; double d = (samplerate / freq); int c = 0; for (int j = index * i; j < index * (i + 1); j += 2) { double deg = 360.0 / d; data[j] = data[j + (channels - 1)] = qSin((c++ * deg) * pi / 180.0) * amplitude; } } return QByteArray((char*)data.data(), data.size() * sizeof(qint16)); } void signalHandler(int signum) { qDebug().nospace() << "Interrupt signal (" << signum << ") received."; qApp->exit(); } int main(int argc, char *argv[]) { //Handle console close to ensure destructors being called #ifdef Q_OS_WIN signal(SIGBREAK, signalHandler); #else signal(SIGHUP, signalHandler); #endif signal(SIGINT, signalHandler); QCoreApplication a(argc, argv); QAudioFormat format; format.setSampleRate(8000); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); AudioOutput output; AudioRecorder file("tone.wav", format); if (!output.start(QAudioDeviceInfo::defaultOutputDevice(), format, 10 * 1000)) //10 seconds of buffer return a.exec(); if (!file.open()) { qDebug() << qPrintable(file.errorString()); return a.exec(); } qDebug() << "Started!"; QByteArray audio_data = tone_generator(); QTimer timer; QObject::connect(&timer, &QTimer::timeout, [&]{ qDebug() << "Writting" << audio_data.size() << "bytes"; output.write(audio_data); file.write(audio_data); }); qDebug() << "Writting" << audio_data.size() << "bytes"; output.write(audio_data); file.write(audio_data); timer.start(8000); //8 seconds because we generated 8 seconds of sound return a.exec(); }
Well. I tried to apply the code (just playback for now) to my situation but there is a problem that it plays data once, if there is no emitting "MorePackets()", or doesn't play at all, if "MorePackets()" occurs. micserver.h #ifndef MICSERVER_H #define MICSERVER_H //-----------------------------// #include <QObject> #include <QDebug> #include <QTcpServer> #include <QTcpSocket> #include <QAudioFormat> #include <QAudioOutput> #include <QFile> #include <QBuffer> #include <QByteArray> #include <QDataStream> #include <iostream> #include "audiooutput.h" //-----------------------------// class MicServer : public QObject { Q_OBJECT public: explicit MicServer(QObject *parent = nullptr); QTcpSocket* Socket; private: QTcpServer* Server; QAudioFormat format; AudioOutput output; QByteArray GetPackets(QTcpSocket* ClientP, int PacketsNumP); signals: void MorePackets(); public slots: void NewConnection(); void Play(); }; //-----------------------------// #endif micserver.cpp #include "micserver.h" //-----------------------------// QByteArray MicServer :: GetPackets(QTcpSocket* ClientP, int PacketsNumP) { QBuffer BufferL; BufferL.open(QBuffer :: WriteOnly); QDataStream InputL(&BufferL); InputL.setVersion(QDataStream::Qt_5_10); QByteArray TempArray; for (int i = 0; i < PacketsNumP; i++) { ClientP -> waitForReadyRead(3000); InputL << ClientP -> readAll(); } for (int i = 0; i < PacketsNumP; i++) { TempArray.push_back(BufferL.buffer().mid(76 + i * 1172, 256)); } BufferL.close(); return TempArray; } //-----------------------------// MicServer :: MicServer(QObject *parent) : QObject(parent) { Server = new QTcpServer; if (!Server -> listen(QHostAddress :: Any, 49112)) { qDebug() << "Failed to launch server!"; } else { qDebug() << "Server launched!"; } connect(Server, SIGNAL(newConnection()), this, SLOT(NewConnection())); connect(Server, SIGNAL(newConnection()), this, SLOT(Play())); connect(this, SIGNAL(MorePackets()), this, SLOT(Play())); format.setSampleRate(16000); format.setChannelCount(1); format.setSampleSize(16); format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt); output.start(QAudioDeviceInfo::defaultOutputDevice(), format); } void MicServer :: NewConnection() { Socket = Server -> nextPendingConnection(); qDebug() << "New connection!"; qDebug() << Socket -> localAddress().toString(); } void MicServer :: Play() { QByteArray audio_data = GetPackets(Socket, 250); QTimer timer; QObject::connect(&timer, &QTimer::timeout, [&]{ qDebug() << "Writting" << audio_data.size() << "bytes"; output.write(audio_data); }); qDebug() << "Writting" << audio_data.size() << "bytes"; output.write(audio_data); timer.start(2000); emit(MorePackets()); }
How to do (dynamic) offscreen render with QOffscreenSurface in Qt5 correctly?
I am try to do some offscreen rendering jobs in Qt5.3 and I want to use QOpenGLFramebufferObject::toImage to output each pictures(I want to output a few pictures when render() draws different things). Following the instructions of this, I succeeded in offscreen rendering my first pic and outputing it. So to future on, I write an example as the following code and here is a package in Google. I can get the first fbo content(and its output file correctly, but from the second time, the fbo was not re-render() and it always output the same pictures). So I want to know what should I do after I finish one time offscreen render to make sure the next time would be correct in qt? Or is there anyone could tell me how to set animation correctly in QOffscreenSurface? qtestofffscreen.h: #ifndef QTESTOFFSCREEN_H #define QTESTOFFSCREEN_H #include <QOffscreenSurface> #include <QWindow> #include <QtGui/QOpenGLFunctions_3_3_Core> #include <QImage> #include <QGLFramebufferObject> #include <QOpenGLPaintDevice> #include <QOpenGLFunctions> #include <QMutex> #include <QMutexLocker> class QTestOffScreen : public QOffscreenSurface, protected QOpenGLFunctions_3_3_Core { Q_OBJECT public: explicit QTestOffScreen( QScreen* targetScreen = nullptr, const QSize& size = QSize (1, 1)); ~QTestOffScreen(); virtual void render(); virtual void initialize(); void renderNow(); void setAnimating(bool animating); bool event(QEvent *) override; void renderLater(); int counts; private: QGLFramebufferObject *fbo; bool m_animating; bool m_update_pending; QOpenGLContext *m_context; QOpenGLPaintDevice *m_device; QSize m_size; QMutex mutex; signals: void doneImg(int index); }; #endif // QTESTOFFSCREEN_H qtestofffscreen.cpp: #include "qtestoffscreen.h" #include <QTime> #include <QDebug> #include <QCoreApplication> #include <QOpenGLFramebufferObject> QTestOffScreen::QTestOffScreen(QScreen *targetScreen, const QSize &size): QOffscreenSurface(targetScreen), m_size(size), fbo(Q_NULLPTR), m_context(Q_NULLPTR), m_device(Q_NULLPTR), counts(100) { requestedFormat().setVersion(3,3); setFormat(requestedFormat()); create(); m_context = new QOpenGLContext(this); m_context->setFormat(format()); if (m_context->create()) { m_context->makeCurrent(this); m_context->functions()->initializeOpenGLFunctions(); }else { delete m_context; m_context = Q_NULLPTR; throw ("Still wrong here"); } //To make sure m_context was initialized //in first time entering renderNow() delete m_context; m_context = Q_NULLPTR; } QTestOffScreen::~QTestOffScreen() { delete m_context; delete m_device; if (fbo) delete fbo; } void QTestOffScreen::render() { glClearColor(0.0f,0.0f,0.0f,1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT | GL_TEXTURE_BIT); glViewport(0,0,1920,1080); glOrtho(0,1920,0,1080,0,1); glColor3f(1.0,0.0,0.0); float tmp = float(qrand()%1000); float count = (float)counts * 10.0f; glLineWidth(3.0f); glBegin(GL_LINE_LOOP); glVertex2f(count ,count ); glVertex2f(count + 100,count); glVertex2f(count + 50,count + 100); glEnd(); qDebug()<<QString("current tmp is %1").arg(count); } void QTestOffScreen::initialize() { if (!fbo) { fbo = new QGLFramebufferObject(1920,1080,GL_TEXTURE_2D); } fbo->bind(); } void QTestOffScreen::renderNow() { bool needsInitialize = false; if (!m_context) { m_context = new QOpenGLContext(this); m_context->setFormat(requestedFormat()); m_context->create(); if (m_context->isValid()) { qDebug()<<"Right Here when creating m_context in renderNow"; } needsInitialize = true; } if ( !m_context->makeCurrent(this) ) { qDebug()<<"This fails in makeCurrent"; } if (needsInitialize) { initializeOpenGLFunctions(); initialize(); } render(); qDebug()<<counts; counts--; fbo->toImage().save(QString::number(counts) + QString(".png")); m_context->doneCurrent(); if (counts >= 0) { m_update_pending = false; emit doneImg(counts); } } void QTestOffScreen::setAnimating(bool animating) { m_animating = animating; m_update_pending = false; if (m_animating) renderLater(); } bool QTestOffScreen::event(QEvent *event) { switch (event->type()) { case QEvent::UpdateRequest: m_update_pending = true; renderNow(); return true; default: return QOffscreenSurface::event(event); } } void QTestOffScreen::renderLater() { if (!m_update_pending) { m_update_pending = true; QCoreApplication::postEvent(this,new QEvent(QEvent::UpdateRequest)); } } void QTestOffScreen::generateImg(QImage *tmp_img_pointer) { GLint viewPort[4]={0}; glGetIntegerv(GL_VIEWPORT,viewPort); int win_width,win_height; win_width = 1920; win_height = 1080; GLubyte *colorArr=new GLubyte[win_width*win_height*3]; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glReadBuffer (GL_FRONT); int tmp_x,tmp_y; tmp_x = 0; tmp_y = 0; glReadPixels(tmp_x,tmp_y,win_width,win_height, GL_RGB,GL_UNSIGNED_BYTE,colorArr); int winrows = tmp_img_pointer->height(); int wincols = tmp_img_pointer->width (); for(int ii=0; ii < winrows * wincols * 3; ii ++) { if((colorArr[ii] <0)|(colorArr[ii] >255)) { colorArr[ii] = 255; } } for(int j=0;j<winrows;j++) for(int i=0;i<wincols;i++) { int index=(j*wincols+i)*3; QRgb value=qRgb(colorArr[index+2], colorArr[index+1], colorArr[index ]); tmp_img_pointer->setPixel(i,winrows-j-1,value); } delete colorArr; } mainwindow.h: #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "qtestoffscreen.h" namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; QTestOffScreen *scr; private slots: void ReceiveCurrentIndex(int); }; #endif // MAINWINDOW_H mainwindow.cpp: #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QSurfaceFormat format; format.setSamples(1); format.setRenderableType(QSurfaceFormat::OpenGL); scr = new QTestOffScreen(); connect(scr,SIGNAL(doneImg(int)),this,SLOT(ReceiveCurrentIndex(int))); scr->setFormat(format); scr->setAnimating(true);//Start rendering animation here. } MainWindow::~MainWindow() { delete ui; } void MainWindow::ReceiveCurrentIndex(int) { Sleep(100); scr->renderLater(); }
Clipboard operation in Graphics View
I have different subclasses for my entities. The entities are drawn in GraphicsView, How can apply the Clipboard operations for Cut, Copy and Paste. gEntity.h #ifndef GENTITY_H #define GENTITY_H class gEntity :public QObject { public: gEntity(QObject* parent=0) : QObject(parent) {} virtual ~gEntity() {} virtual gEntity* my_clone() { return 0; } }; #endif // GENTITY_H circle.h #ifndef CIRCLE_H #define CIRCLE_H #include <QPainter> #include <QGraphicsItem> #include <gentity.h> #include "qmath.h" class Circle : public QObject, public QGraphicsItem, public gEntity { Q_OBJECT public: Circle(QObject* parent=0) : gEntity(parent){ } Circle(int, QPointF, QPointF); Circle(int, QPointF, qreal); QRectF boundingRect() const; virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); enum { Type = UserType + 3 }; int type() const; int id; QPointF center_p, end_p, move_p; qreal radius; void setRadius((const qreal &radius); private: QVector<QPointF> stuff; }; #endif // CIRCLE_H circle.cpp #include "circle.h" #include "gentity.h" Circle::Circle(int i, QPointF p1, QPointF p2) { // assigns id id = i; /* set values of center point, end point and calculate radius of circle */ center_p = p1; end_p = p2; radius = qSqrt(qPow((end_p.x()-center_p.x()), 2) + qPow((end_p.y()-center_p.y()), 2)); } Circle::Circle(int i, QPointF p1, qreal rad) { // assigns id id = i; /* set values of center point and radius of circle */ center_p = p1; radius = rad; } int Circle::type() const { // Enable the use of qgraphicsitem_cast with circle item. return Type; } QRectF Circle::boundingRect() const { // bounding rectangle for circle return QRectF((center_p.x()-radius), (center_p.y()-radius), (2*radius), (2*radius)); } void Circle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // draws/paints the path of circle QPen paintpen(Qt::black); paintpen.setWidth(1); painter->setRenderHint(QPainter::Antialiasing); if (isSelected()) { // sets brush for center point painter->setBrush(Qt::SolidPattern); paintpen.setColor(Qt::red); painter->setPen(paintpen); painter->drawEllipse(center_p, 2, 2); // sets pen for circumference paintpen.setStyle(Qt::DashLine); paintpen.setColor(Qt::black); painter->setBrush(Qt::NoBrush); painter->setPen(paintpen); painter->drawEllipse(center_p, radius, radius); } else { painter->save(); painter->setBrush(Qt::SolidPattern); paintpen.setColor(Qt::black); painter->setPen(paintpen); painter->drawEllipse(center_p, 2, 2); painter->setBrush(Qt::NoBrush); painter->drawEllipse(center_p, radius, radius); painter->restore(); } } gEntity* my_clone(){ Circle *c = new Circle(); c->setRadius(radius); return c; } Clipboard.h #include<QStack> #include<QClipboard> class MyClipBoard { public: static MyClipBoard* instance() { if(!inst) inst = new MyClipBoard; return inst; } void push(gEntity* g) { clips.push(g); } gEntity* last() { if(clips.count() == 0) return 0; return clips.last(); } gEntity* pop() { if(clips.count() == 0) return 0; return clips.pop(); } bool isempty() const { return clips.empty(); } private: QStack<gEntity*> clips; static MyClipBoard* inst; }; CadgraphicsScene.cpp MyClipBoard* MyClipBoard::inst = 0; CadGraphicsScene::CadGraphicsScene(QObject *parent, QUndoStack *undoStack) : QGraphicsScene(parent) { setFlags(); id = 0; mUndoStack = undoStack; m_context = new QMenu; m_context->addAction("Insert Circle"); a_cut = m_context->addAction("cut"); a_copy = m_context->addAction("copy"); a_paste = m_context->addAction("paste"); context_item = 0; connect(m_context, SIGNAL(triggered(QAction*)), this, SLOT(contmenu(QAction*))); } void CadGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event){ m_context->exec(event->screenPos()); } void CadGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { // mousePressEvent in the graphicsScene if(mouseEvent->button() == Qt::LeftButton) { switch (entityMode) { case NoMode: qDebug() << "No Mode"; break; case PointMode: pointItem = new Point(++id); pointItem->setPos(mouseEvent->scenePos()); itemList.append(pointItem); mUndoStack->push(new CadCommandAdd(this, pointItem)); break; case LineMode: if (mFirstClick) { start_p = mouseEvent->scenePos(); mFirstClick = false; mSecondClick = true; } else if (!mFirstClick && mSecondClick) { end_p = mouseEvent->scenePos(); mPaintFlag = true; mSecondClick = false; } if (mPaintFlag) { lineItem = new Line(++id, start_p, end_p); lineItem->setLine(start_p.x(), start_p.y(), end_p.x(), end_p.y()); itemList.append(lineItem); mUndoStack->push(new CadCommandAdd(this, lineItem)); setFlags(); } break; case CircleMode: if (mFirstClick) { start_p = mouseEvent->scenePos(); mFirstClick = false; mSecondClick = true; } else if (!mFirstClick && mSecondClick) { end_p = mouseEvent->scenePos(); mPaintFlag = true; mSecondClick = false; } if (mPaintFlag) { circleItem = new Circle(++id, start_p, end_p); itemList.append(circleItem); mUndoStack->push(new CadCommandAdd(this, circleItem)); setFlags(); } break; case EllipseMode: if (mFirstClick) { start_p = mouseEvent->scenePos(); mFirstClick = false; mSecondClick = true; } else if (!mFirstClick && mSecondClick) { mid_p = mouseEvent->scenePos(); mFirstClick = false; mSecondClick = false; mThirdClick = true; } else if (!mSecondClick && mThirdClick) { end_p = mouseEvent->scenePos(); mThirdClick = false; mPaintFlag = true; } if (mPaintFlag) { ellipseItem = new Ellipse(++id, start_p, mid_p, end_p); itemList.append(ellipseItem); mUndoStack->push(new CadCommandAdd(this, ellipseItem)); setFlags(); } break; case TextMode: textItem = new mText(++id); textItem->setPos(mouseEvent->scenePos()); itemList.append(textItem); textItem->setTextInteractionFlags(Qt::TextEditorInteraction); mUndoStack->push(new CadCommandAdd(this, textItem)); connect(textItem, SIGNAL(lostFocus(mText*)), this, SLOT(editorLostFocus(mText*))); connect(textItem, SIGNAL(selectedChange(QGraphicsItem*)), this, SIGNAL(itemSelected(QGraphicsItem*))); setFlags(); default: ; } }else if(event->button() & Qt::RightButton) { context_item = itemAt(event->scenePos().toPoint(), QTransform());//base operand not a pointer cpos = event->scenePos();//says invalid use of member function if(!context_item)//Here it says all variables out of scope { a_cut->setEnabled(false); a_copy->setEnabled(false); if(MyClipBoard::instance()->isempty()) a_paste->setEnabled(false); else a_paste->setEnabled(true); } else { a_cut->setEnabled(true); a_copy->setEnabled(true); a_paste->setEnabled(false); } } QGraphicsScene::mousePressEvent(mouseEvent); } cadgraphicsscene.h private: QMenu* m_context; QAction* a_cut; QAction* a_copy; QAction* a_paste; QGraphicsItem* context_item; QPointF cpos;
what are you mean of cutting a grahicsitem? (change its position or move to another view) for copy and paste search for cloning a object in c++ it's not hard, and in paste just change position of new object and add that to your graphicsview or scene update: #ifndef MYSCENE_H #define MYSCENE_H #include <QGraphicsView> #include <QGraphicsScene> #include <QGraphicsItem> #include <QGraphicsSceneContextMenuEvent> #include <QMenu> #include <QStack> class gEntity : public QObject, public QGraphicsItem { public: gEntity(QObject* parent=0) : QObject(parent) {} virtual ~gEntity() {} virtual gEntity* my_clone() { return 0; } }; class Circle : public gEntity { public: Circle(QObject* parent=0) : gEntity(parent) { m_radius = 10; } qreal radius() const; void setRadius(const qreal &radius); gEntity* my_clone() { Circle* c = new Circle; c->setRadius(m_radius); return c; } QRectF boundingRect() const { return QRectF(-m_radius, -m_radius, 2 * m_radius, 2 * m_radius); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->save(); painter->setBrush(Qt::yellow); painter->drawEllipse(QPointF(0,0), m_radius, m_radius); painter->restore(); } private: qreal m_radius; }; class MyClipBoard { public: static MyClipBoard* instance() { if(!inst) inst = new MyClipBoard; return inst; } void push(gEntity* g) { clips.push(g); } gEntity* last() { if(clips.count() == 0) return 0; return clips.last(); } gEntity* pop() { if(clips.count() == 0) return 0; return clips.pop(); } bool isempty() const { return clips.empty(); } private: QStack<gEntity*> clips; static MyClipBoard* inst; }; class MyScene : public QGraphicsScene { Q_OBJECT public: MyScene(QObject* parent=0); virtual ~MyScene() { delete m_context; } protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); void mousePressEvent(QGraphicsSceneMouseEvent* event); public slots: void insertCircle(const QPointF& pos) { Circle* mcircle = new Circle; addItem(mcircle); mcircle->setPos(pos); } void cut(gEntity* obj) { removeItem(obj); MyClipBoard::instance()->push(obj); } void copy(gEntity* obj) { MyClipBoard::instance()->push(obj->my_clone()); } void paste(const QPointF& pos) { gEntity* last = MyClipBoard::instance()->pop(); if(last) { addItem(last); last->setPos(pos); } } void contmenu(QAction* a) { if(a->text() == "Insert Circle") { insertCircle(cpos); } else if(a == a_cut) { cut(static_cast<gEntity*>(context_item)); } else if(a == a_copy) { copy(static_cast<gEntity*>(context_item)); } else if(a == a_paste) { paste(cpos); } } private: QMenu* m_context; QAction* a_cut; QAction* a_copy; QAction* a_paste; QGraphicsItem* context_item; QPointF cpos; }; #endif // MYSCENE_H scene.cpp #include "myscene.h" #include <QGraphicsSceneMouseEvent> MyClipBoard* MyClipBoard::inst = 0; qreal Circle::radius() const { return m_radius; } void Circle::setRadius(const qreal &radius) { m_radius = radius; } MyScene::MyScene(QObject *parent) : QGraphicsScene(parent) { m_context = new QMenu; m_context->addAction("Insert Circle"); a_cut = m_context->addAction("cut"); a_copy = m_context->addAction("copy"); a_paste = m_context->addAction("paste"); context_item = 0; connect(m_context, SIGNAL(triggered(QAction*)), this, SLOT(contmenu(QAction*))); } void MyScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event){ m_context->exec(event->screenPos()); } void MyScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mousePressEvent(event); if(event->button() & Qt::RightButton) { context_item = itemAt(event->scenePos().toPoint(), QTransform()); cpos = event->scenePos(); if(!context_item) { a_cut->setEnabled(false); a_copy->setEnabled(false); if(MyClipBoard::instance()->isempty()) a_paste->setEnabled(false); else a_paste->setEnabled(true); } else { a_cut->setEnabled(true); a_copy->setEnabled(true); a_paste->setEnabled(false); } } } main.cpp #include <QApplication> #include "myscene.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); QGraphicsView w(new MyScene); w.setSceneRect(0,0,500,500); w.show(); return a.exec(); }