禁房艳奇在线观看:画出wav文件声音数据的波形曲线

来源:百度文库 编辑:偶看新闻 时间:2024/05/06 04:51:10
画出wav文件声音数据的波形曲线2010-08-08 21:41

继续上一篇《解读Wave文件头》,更具体的解析wave文件格式,并附带代码。但是下文中画波形图的数据并未经过傅里叶转换,下面这个链接提供了快速傅里叶转换后的数据制作波形图的代码(确切的说,这两个程序画出的波形图并不是同类图形):
http://www.codeproject.com/KB/audio-video/DirectSound_Spectrum2.aspx

画出wav文件声音数据的波形曲线
转自:http://blog.csdn.net/khler/archive/2007/07/28/1713697.aspx

WAV文件格式
波形音频文件(*.WAV)是Microsoft为Windows设计的多媒体文件格式RIFF(The Resource Interchange File Format,资源交换文件格式)中的一种(另一种常用的为AVI)。RIFF由文件头、数据类型标识及若干块(chunk)组成。

WAV文件的基本格式



注意:
wFormatTag = 1时为无压缩的PCM(Pulse Code Modulation, 脉冲编码调制)标准格式(即等间隔采样、线性量化)。
单字节样本值v为无符号整数(0~255),实际样本值应为v-128;多字节样本值本身就是有符号的,可直接使用。
有些wav文件在data块之前,fmt块之后还有一个fact块..
| ID    | 4 Bytes |   'fact'          |
----------------------------------
| Size | 4 Bytes |   数值为4    |
----------------------------------
| data | 4 Bytes | ?? ?? ?? ?? |
因此要根据读到的ID进行判断
参考: http://www.snowcn.net/?action/viewspace/itemid/260.html文件格式分析详解 wav


Wav文件 所有数值表示均为低字节表示低位,高字节表示高位。
通过CArchive的>>读入, 会自动转化(把高字节的作为高位)
如读入地址为0000000的双字(DWORD)到变量dw中 :
0000000: 52 49 46 46
则dw会等于0x46464952

为了简化RIFF文件中的4字符标识的读写与比较,Windows SDK在多媒体头文件mmsystem.h中定义了类型
FOURCC(Four-Character Code四字符代码):
typedef DWORD FOURCC;
及其构造宏(用于将4个字符转换成一个FOURCC数据)
FOURCC mmioFOURCC(CHAR ch0, CHAR ch1, CHAR ch2, CHAR ch3);
其定义为MAKEFOURCC宏:
#define mmioFOURCC(ch0, ch1, ch2, ch3) MAKEFOURCC(ch0, ch1, ch2, ch3);
而MAKEFOURCC宏定义为:
#define MAKEFOURCC(ch0, ch1, ch2, ch3) \
    ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \
    ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 ));
例如:
#include
#define ID_RIFF    mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE   mmioFOURCC('W', 'A', 'V', 'E')
……
FOURCC id;
……
              ar >> id;
              if (id != ID_RIFF) {
       ……
}
……
播放波形声音文件
函数PlaySound可以播放系统声音、声音资源和声音文件,其函数原型为:
BOOL PlaySound(
LPCSTR pszSound,
HMODULE hmod,    
DWORD fdwSound
);
例如:
PlaySound(“c:\\sounds\\sample.wav”, NULL, SND_ASYNC);
              PlaySound(ar.GetFile()->GetFilePath(), NULL, SND_ASYNC);

下面是完整步骤:
新建MFC应用程序, 单文挡(SDI)项目WavePlayer.
为了使包含PlaySound的程序能够编译通过,必须包含多媒体头文件: #include , 而且需要注意头文件包含的次序.. 否则会提示找不到标识符..
并在项目中添加多媒体库:在项目区中任何页中选中顶部的项目名,选“项目\属性”菜单项或按Alt+F7组合键,弹出“[项目名]属性页”对话框,在该其左上角的“配置”栏的下拉式列表中,选择“所有配置”项,在其左边的“配置”目录栏中,选中“配置属性\链接器\输入”项,在右边顶行的“附加依赖项”栏中键入winmm.lib,按“确定”钮关闭对话框。

文件过滤
可通过修改资源视图页的“项目名\项目名.rc\String Table\String Table”串表资源中的ID:IDR_MAINFRAME(SDI)所对应的串,为应用程序的文件I/O对话框增加文件过滤器。为WavePlayer程序增加*.wav的过滤器:
将原来的串
IDR_MAINFRAME “WavePlayer\n\nWavePlayer\n\n\nWavePlayer.Document\nWavePlayer.Document”
修改成
“WavePlayer\n\nWavePlayer\nWave Files (*.wav)\n.wav\nWavePlayer.Document\nWavePlayer.Document”


文档类cpp 添加如下宏
#define ID_RIFF mmioFOURCC('R', 'I', 'F', 'F')
#define ID_WAVE mmioFOURCC('W', 'A', 'V', 'E')
#define ID_data mmioFOURCC('d', 'a', 't', 'a')
#define ID_fmt mmioFOURCC('f', 'm', 't', '\x20')
#define ID_fact mmioFOURCC('f', 'a', 'c', 't')


文档类添加如下变量
public:
    int num;         // 样本数
    LONG* data;      // 样本数据
    LONG *Ldata, *Rdata;     // 双声道数据
    WORD BytesPerSample, wChannel;     // 一个样本的字节数, 声道数

Serialize函数添加如下代码:
if (ar.IsStoring())
    {
        // TODO: 在此添加存储代码
    }
    else
    {
        // TODO: 在此添加加载代码
        FOURCC id, chkLen, dw;    // 无符号双字
        BYTE b;               // 无符号字节
        WORD w;              // unsined short 无符号单字长
        WORD fmtTag;           //格式标记
        WORD wBitsPerSample;   //样本位数
        FOURCC dwAvgBytesRate;
        FOURCC Len;          // 数据块大小, BYTE
        ar>>id;
        if(id!=ID_RIFF) return;
        ar>>dw>>id;
        if(id!=ID_WAVE) return;
        ar>>id; if(id!=ID_fmt) return;
        ar>>chkLen;            // 16或
        ar>>fmtTag;            // 只处理PCM(Pulse Code Modulation, 脉冲编码调制)情况
        if(fmtTag!=1)
            return;
        ar>>wChannel;           //声道数
        ar>>dw;                 //采样率
        ar>>dwAvgBytesRate;     //平均字节率
        ar>>w;                  //数据块对齐
        ar>>wBitsPerSample;     //样本位数
        if(chkLen==18)
        {
            ar>>w;               // 扩展域大小
            for(int i=0; i                ar>>b;
        }
        ar>>id;            // data or fact
        if(id==ID_fact)
        {
            ar>>dw>>dw;        // 读走fact块内容
            ar>>id;    //      data ID
        }
        if(id!=ID_data)
            return;
        ar>>Len;         
        BytesPerSample=wBitsPerSample/8;         // 一个样本的字节数
        num=Len/BytesPerSample;              // 样本数
        if(wChannel==1)                 // 单音道
        {
            data=new LONG[num];
            if(BytesPerSample==1)
                for(int i=0; i                {
                    ar>>b;
                    data[i]=b-128;
                }
            else if(BytesPerSample==2)
                for(int i=0; i                {
                    ar>>w;
                    data[i]= (SHORT)w;         // 无符号数转成有符号数
                }
            else if(BytesPerSample==4)
                for(int i=0; i                {
                    ar>>dw;
                    data[i]=(LONG)dw;          // 无符号数转成有符号数
                }
            else if(BytesPerSample==3)
                for(int i=0; i                {
                    ar>>b>>w;
                    data[i]=w+b;
                }
        }
        else if(wChannel==2)             // 双音道
        {
            Ldata=new LONG[num/2];     // 左声道
            Rdata=new LONG[num/2];      // 右声道
            if(BytesPerSample==1)
            {
                for(int i=0; i                {
                    ar>>b;           
                    Ldata[i]=b-128;       
                    ar>>b;
                    Rdata[i]=b-128;
                }
            }
            else if(BytesPerSample==2)
                for(int i=0; i                {
                    ar>>w;
                    Ldata[i]=(SHORT)w;         // 无符号数转成有符号数
                    ar>>w;
                    Rdata[i]=(SHORT)w;
                }
            else if(BytesPerSample==4)
                for(int i=0; i                {
                    ar>>dw;
                    Ldata[i]=(LONG)dw;
                    ar>>dw;
                    Rdata[i]=(LONG)dw;
                }
            else if(BytesPerSample==3)
                for(int i=0; i                {
                    ar>>b>>w;
                    Ldata[i]=w+b;
                    ar>>b>>w;
                    Rdata[i]=w+b;
                }
        }
        PlaySound(ar.GetFile()->GetFilePath(), NULL, SND_ASYNC); // 播放声音
    }



视图类OnDraw函数添加如下代码:
CRect rect;                // 客户区大小
    GetClientRect(&rect);       
    CPen gpen(PS_SOLID, 1, RGB(0, 250, 0));          // 绿色笔
    pDC->SelectObject(&gpen);
    if(pDoc->data!=NULL || pDoc->Ldata!=NULL)        // 数据非空时才画
    {
        float A=pow(2.0, 8.0*pDoc->BytesPerSample-1); // 将样本的高度映射到所需高度,
                                                       // 先算出样本的最大值
        if(pDoc->wChannel==1)                         // 单声道
        {
            int x=0, y=rect.Height()/2;              
            while(x < rect.Width())        
            {
                int min=INT_MAX, max=INT_MAX+1;      // 让min初始为最大的int, 让max初始化最小的int
                // 一个象素x映射(样本数/客户区宽度)个样本点, 用其中最大最小值, 画一竖直的线段
                for(int j=x*pDoc->num/rect.Width(); j<(x+1)*pDoc->num/rect.Width(); j++)
                {
                    if(pDoc->data[j]                        min=pDoc->data[j];
                    if(pDoc->data[j]>max)
                        max=pDoc->data[j];
                }
                pDC->MoveTo(x, y+(max*rect.Height()/2.0/A));
                pDC->LineTo(x, y+(min*rect.Height()/2.0/A));
                x++;
            }
            CPen pen(PS_SOLID, 1, RGB(200, 0, 0));          // 客户区中间画一横线
            pDC->SelectObject(&pen);
            pDC->MoveTo(0, rect.Height()/2);
            pDC->LineTo(rect.Width(), rect.Height()/2);   
        }
        else if(pDoc->wChannel==2)
        {
            // 在客户区上半部分画左声道, 原理同单声道,
            int x=0, y=rect.Height()/4;
            while(x            {
                int min=INT_MAX, max=INT_MAX+1;
                for(int j=x*pDoc->num/2/rect.Width(); j<(x+1)*pDoc->num/2/rect.Width(); j++)
                {
                    if(pDoc->Ldata[j]                        min=pDoc->Ldata[j];
                    if(pDoc->Ldata[j]>max)
                        max=pDoc->Ldata[j];
                }
                pDC->MoveTo(x, y+(max*rect.Height()/4.0/A));
                pDC->LineTo(x, y+(min*rect.Height()/4.0/A));
                x++;
            }
            // 在客户区下半部分画右声道
            x=0, y=3*rect.Height()/4;
            while(x            {
                int min=INT_MAX, max=INT_MAX+1;
                for(int j=x*pDoc->num/2/rect.Width(); j<(x+1)*pDoc->num/2/rect.Width(); j++)
                {
                    if(pDoc->Rdata[j]                        min=pDoc->Rdata[j];
                    if(pDoc->Rdata[j]>max)
                        max=pDoc->Rdata[j];
                }
                gpen.DeleteObject();
                gpen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
                pDC->SelectObject(&gpen);
                pDC->MoveTo(x, y+(max*rect.Height()/4.0/A));
                pDC->LineTo(x, y+(min*rect.Height()/4.0/A));
                x++;
            }
            // 画客户区中央横线
            CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
            pDC->SelectObject(&pen);
            pDC->MoveTo(0, rect.Height()/2);
            pDC->LineTo(rect.Width(), rect.Height()/2);
            pen.DeleteObject();
            pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 255));
            pDC->SelectObject(&pen);
            // 画左声道横线
            pDC->MoveTo(0, rect.Height()/4);
            pDC->LineTo(rect.Width(), rect.Height()/4);
            // 右声道横线
            pDC->MoveTo(0, 3*rect.Height()/4);
            pDC->LineTo(rect.Width(), 3*rect.Height()/4);
        }
    }

OnDraw用到了pow函数, 添加头文件包含指令
#include      // double pow(double, double);


(原文出处:http://blog.csdn.net/touzani/archive/2007/06/17/1654943.aspx)