任务:用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中,后续文章会写到~