您的位置:首页 > 编程语言 > Qt开发

Qt5绘制wav波形图

2013-05-30 22:20 801 查看
任务:用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中,后续文章会写到~

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Qt5 C++ 波形图