任务:用qt5画wav波形图
步骤:
1、新建项目 - Qt Gui 应用 - 工程名 - 基类选择QWidget(类名默认为widget) - 完成,生成widget.h, main.cpp, widget.cpp
2、接下来要添加一个自定义类,用来读入wav头文件信息,以及读入wav数据。
向项目添加新建头文件 WaveFile.h
#ifndef WAVEFILE_H
#define WAVEFILE_H
#include <string>
using namespace std;
class WaveFile
{
public:
struct wavehead
{
char sign[4]; //"RIFF"标志 4
unsigned long int flength; //文件长度 8
char wavesign[4]; //"WAVE"标志 12
char fmtsign[4]; //"fmt"标志 16
unsigned long int unused; // 过渡字节(不定)20
unsigned short formattype; //格式类别(10H为PCM形式的声音数据) 22
unsigned short channelnum; //通道数,单声道为1,双声道为2 24
unsigned long int samplerate; //采样率(每秒样本数),表示每个通道的播放速度 28
unsigned long int transferrate; //传输速率,每秒字节数
unsigned short int adjustnum; //数据调整数,一个数据单位所占的字节
unsigned short int databitnum; //每样本的数据位数,调整数*8 36
}head;
unsigned long int datalength; //采样数据总数
unsigned long int totalsample; //采样点数
unsigned long int bitpersample; //采样位数
unsigned long int datanum; //数据块大小,若采样位数为16,开辟数据总数的大小,若为8,每个short型高地位可存储两个数据,开辟1/2大小即可
short *Data; //数据块指针
WaveFile() {}
~WaveFile() {}
void WavRead(string filename)
{
FILE *fp;
if((fp=fopen(filename.c_str(),"rb"))==NULL)
{
//printf("cannot read wave file\n");
exit(0);
}
fread(&head,sizeof(head),1,fp);
char datasign[4];
fread(datasign,4,1,fp);
fread(&datalength,4,1,fp);
totalsample = datalength / head.adjustnum;
bitpersample = head.databitnum / head.channelnum;
datanum = totalsample*bitpersample/16;
Data = new short[datanum+10]; //开辟数据块,若采样位数为16,开辟数据总数的大小,若为8,每个short型高地位可存储两个数据,开辟1/2大小即可
if(bitpersample==16)
{
for(int i=0;!feof(fp) && i<datanum;i++) //读入数据
{
fread(&Data[i],2,1,fp);
if(head.channelnum==2) //若是双声道,跳过第二个声道
fseek(fp,2,SEEK_CUR);
}
}
else
{
for(int i=0;!feof(fp) && i<datanum;i++) //读入数据
{
short low,high;
fread(&low,1,1,fp);
if(head.channelnum==2) //若是双声道,跳过第二个声道
fseek(fp,1,SEEK_CUR);
fread(&high,1,1,fp);
if(head.channelnum==2) //若是双声道,跳过第二个声道
fseek(fp,1,SEEK_CUR);
Data[i]= (low & 0x00ff) | (high << 8 & 0xff00);
}
}
fclose(fp);
}
};
#endif // WAVEFILE_H
3、修改widget.h文件:
(1)由于要用WaveFile类声明实例:#include "WaveFile.h"
(2)声明重载绘图函数:voidpaintEvent(QPaintEvent*);
(3)声明绘制波形图所需的函数和变量
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "WaveFile.h"
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void paintEvent(QPaintEvent *);
private:
Ui::Widget *ui;
void Draw16Bit(QPainter &p,int W,int H);
void Draw8Bit(QPainter &p,int W,int H);
//波形图所需变量
WaveFile m_Wavefile;
bool m_DrawWave;
QString m_Filename;
double m_SamplesPerPixel;
double m_ZoomFactor;
int m_OffsetInSamples;
};
#endif // WIDGET_H
4、修改widget.cpp文件:
(1)包含绘图基类:#include <QPainter>
(2)修改构造函数,初始化绘图所需的成员变量
(3)定义绘图函数:void Widget::paintEvent(QPaintEvent*e)
几点注意:1)m_Filename变量存储wav路径
2)采用单缓冲方法,先把图画在一个位图QPixmappix 上,为位图创建一个QPainter对象p来画图,再一下载入到画布上
3)绘图函数(绘制线):p.drawLine(x1, y1, x2, y2); 从x1,y1到x2,y2画一条线。可以先用p.setPen(pen) 设置画笔
#include "widget.h"
#include "ui_widget.h"
#include <QPainter>
#include <qmessagebox.h>
#include <QTextCodec>
#define MIN(x,y) (x)<(y)?(x):(y)
#define MAX(x,y) (x)>(y)?(x):(y)
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget),
{
ui->setupUi(this);
//初始化成员变量
m_SamplesPerPixel = 0.0;
m_OffsetInSamples = 0;
m_Filename = "";
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *e)
{
m_Filename = "D:\\PRIS\\test.wav";
if(m_Filename == "")
return;
int W = this->width(); //900;
int H = this->height();//350;
QPixmap pix(W,H); //以此为参数创建一个位图变量
pix.fill(Qt::black);//填充位图背景色
QPainter p(&pix); //以位图为参数创建一个QPainter 对象
//p.translate(-ur.x(),-ur.y()); //平移坐标系
//p.drawRect(100,100,200,100);
QPen pen;
pen.setColor(QColor(00, 35, 00)); //背景网格画笔
p.setPen(pen); //设置画笔
//画背景网格图
int i;
double x, y;
int col_num = 30; //背景网格列数
int row_num = 13; //背景网格行数
for (i = 0; i <= col_num; i++) //画竖线
{
x = i * W / col_num;
p.drawLine((int)x, 0, (int)x, H);
}
for (i = 0; i <= row_num; i++) //画横线
{
y = i * H / row_num;
p.drawLine(0, (int)y, W, (int)y);
}
pen.setColor(QColor(0x4B,0xF3,0xA7)); //波形图画笔
p.setPen(pen); //设置画笔
//m_Wavefile = new WaveFile();
m_Wavefile.WavRead(m_Filename.toStdString());
//QMessageBox::information(this,"Information",tr("anything you want tell user"));
m_DrawWave = true;
if (m_DrawWave)
{
//RectangleF visBounds = grfx.VisibleClipBounds;
if (m_SamplesPerPixel == 0.0) //计算每个像素要显示多少采样点
{
m_SamplesPerPixel = (m_Wavefile.datanum / W);
}
p.drawLine(0, H / 2, W, H / 2);
//p.translate(0,H/2);
//grfx.ScaleTransform(1, -1);
if (m_Wavefile.bitpersample == 16)
Draw16Bit(p,W,H);
else if (m_Wavefile.bitpersample == 8)
Draw8Bit(p,W,H);
//QMessageBox::information(this,"Information",tr(s.c_str()));
}
QPainter painter(this); //获取当前画布
painter.drawPixmap(0,0,pix); //将位图画到画布上,单缓冲技术
}
void Widget::Draw16Bit(QPainter &p,int W,int H)
{
int prevX = 0;
int prevY = 0;
int i = 0;
// index is how far to offset into the data array
int index = m_OffsetInSamples;
int maxSampleToShow = (int)((m_SamplesPerPixel * W) + m_OffsetInSamples);
maxSampleToShow = MIN(maxSampleToShow, m_Wavefile.datanum);
while (index < maxSampleToShow)
{
short maxVal = -32767;
short minVal = 32767;
// finds the max & min peaks for this pixel
for (int x = 0; x < m_SamplesPerPixel; x++)
{
maxVal = MAX(maxVal, m_Wavefile.Data[x + index]);
minVal = MIN(minVal, m_Wavefile.Data[x + index]);
}
// scales based on height of window
int scaledMinVal = (int)(((minVal + 32768) * H) / 65536);
int scaledMaxVal = (int)(((maxVal + 32768) * H) / 65536);
scaledMinVal = H - scaledMinVal;
scaledMaxVal = H - scaledMaxVal;
// if samples per pixel is small or less than zero, we are out of zoom range, so don't display anything
if (m_SamplesPerPixel > 0.0000000001)
{
// if the max/min are the same, then draw a line from the previous position,
// otherwise we will not see anything
if (scaledMinVal == scaledMaxVal)
{
if (prevY != 0)
p.drawLine(prevX, prevY, i, scaledMaxVal);
}
else
{
p.drawLine(i, scaledMinVal, i, scaledMaxVal);
}
}
else
return;
prevX = i;
prevY = scaledMaxVal;
i++;
index = (int)(i * m_SamplesPerPixel) + m_OffsetInSamples;
}
}
void Widget::Draw8Bit(QPainter &p,int W,int H)
{
int prevX = 0;
int prevY = 0;
int i = 0;
// index is how far to offset into the data array
int index = m_OffsetInSamples;
int maxSampleToShow = (int)((m_SamplesPerPixel * W) + m_OffsetInSamples);
maxSampleToShow = MIN(maxSampleToShow, m_Wavefile.datanum);
while (index < maxSampleToShow)
{
short maxVal = 0;
short minVal = 255;
// finds the max & min peaks for this pixel
for (int x = 0; x < m_SamplesPerPixel; x++)
{
short low, high;
low = (short)(m_Wavefile.Data[x + index] & 0x00ff);
high = (short)(m_Wavefile.Data[x + index] >> 8 & 0x00ff);
maxVal = MAX(maxVal, low);
minVal = MIN(minVal, low);
maxVal = MAX(maxVal, high);
minVal = MIN(minVal, high);
}
// scales based on height of window
int scaledMinVal = (int)(((minVal) * H) / 256);
int scaledMaxVal = (int)(((maxVal) * H) / 256);
scaledMinVal = H - scaledMinVal;
scaledMaxVal = H - scaledMaxVal;
// if samples per pixel is small or less than zero, we are out of zoom range, so don't display anything
if (m_SamplesPerPixel > 0.0000000001)
{
// if the max/min are the same, then draw a line from the previous position,
// otherwise we will not see anything
if (scaledMinVal == scaledMaxVal)
{
if (prevY != 0)
p.drawLine(prevX, prevY, i, scaledMaxVal);
}
else
{
p.drawLine(i, scaledMinVal, i, scaledMaxVal);
}
}
else
return;
prevX = i;
prevY = scaledMaxVal;
i++;
index = (int)(i * m_SamplesPerPixel) + m_OffsetInSamples;
}
}
5、运行程序即可,由于此程序是直接把wav路径赋值为常量,只是一个测试程序,接下来需要把这个widget类嵌入到另一个mainwindow中,后续文章会写到~