目前位置: VCer资源中心 >>> VCer开源 >>> 可重用类

[本帖已阅读4736次 分值205 回复1次] 张贴资源 发回信箱 控制面板

snoopy每日一译-CWaveFile--一个操作和表示WAV数据的类

提供者:ycr40 张贴时间:2004-04-28 22:05:32.0 出处:vcer.net 作者:不祥

snoopy每日一译-CWaveFile--一个操作和表示WAV数据的类(2004-04-28 22:05:32.0)


snoopy


 
级别: VCer排长
头衔: VCer会员

经验: 1285
作品: 28
分会: 华南分会
注册: 2004-04-15 09:22:35.0
登录: 2004-05-18 08:45:59.0
工程源码[3,781字节] 下载637
软件下载[81,122字节] 下载540

实现方法

介绍:

我先从简单介绍数字声音和它在计算机中的文档开始。很久很久以前,声音信号,像其他信号一样,用连续波形表示。它们被称作模拟信号。

模拟信号有很多优点,其中一个优点是它和物理变化一一对应。例如:当我们说话,我们的声带发生震动,声波通过空气传播。使用模拟仪器,我们很容易记录和保存声波(例如使用磁带)。但模拟信号也有一个很不好的缺点:抗干扰能力差。

数字信号没有这个缺点,因为数字表示可以有冗余数据。通过冗余数据的信息,即使传输过程中信号发生严重变化,也可以恢复原来的信号。因此数据信号被广泛使用:通讯、领航、医药、声音处理、计算机等。

我知道你更干兴趣的问题是:数字信号在计算机中是怎样存储的?我怎样处理它?我不想深入解释数字信号原理。你,作为一个程序员,必须知道的只有一件事:数字信号是一个数组(你会得到你自己的数组,如果你读完这篇文章的话)。对于声音数字信号,它可以是8位或16位的数字。

现在有大量的声音数字信号存储的标准(AU, VOC, WAVE, AIFF, AIFF-C, and IFF/8VX),但是实际上,微软的WAV文件使用得最广泛。

WAVE文件格式:

所有的WAVE文件符合RIFF规范。因此,WAVE文件满足以下条件:

由独立的数据块(称为chunk)组成,这些数据块组织称树状结构。

每个数据块由一个块头和数据组成。

RIFF文件的第一块(也是主要的块)是一个RIFF块,它像树的根。

通常的WAVE PCM文件是这样的:

文件的开始是RIFF头,然后是FMT块和DATA块。RIFF头有三个元素:RIFF_ID,RIFF_SIZE,RIFF_FORMAT:

RIFF头之后,是格式描述块format descriptor block (FMT):

WAVRFORMAT机构是理解WAVE文件的关键所在。它包含我们处理WAVE文件中的所需要的信息。

最后是数据块:

这就是你所需要知道的WAVE文件头;文件头之后是数据。让我们看看CWaveFile接口:

正如你所看到的,CWaveFile从CObject和CFileMap继承。

内存映射文件:

内存映射文件是一个windows系统中非常有用的特性。我第一次实现CWaveFile类的时候,我不知道内存映射文件,我的代码使用缓冲区,从缓冲区中拷贝内容并...产生很多问题(虽然它可以使用)。内存映射文件是通过指针将操作磁盘文件变成如操作内存一样方便的技术。内存映射文件的操作快速而容易;另外,你不需要缓冲区。我在网上找到了Vitali Brusetsev的对内存映射文件封状的类(感谢Vitali!)。他的类封装了你需要的所有函数。因此,我问他在我的程序中使用他的类,并得到了肯定的回答。我选择他的类作为我的CWaveFile类的基类。

使用CWaveFile:

在你的工程中很容易使用CWaveFile。CWaveFile只有一个构造器,并且它只有一个参数--WAVE文件的路径。如果出错,CWaveFile会产生C++异常,所有的异常组织在下面的名字空间:

还有另外一些标识符号不匹配的异常会发生。顺便说说,我在微软的"录音机"写入的WAVE文件中发现了一些有趣的特性:数据在这些文件中被转换称6位,它从50字节开始。因此我这样做:

如果DATA标识不匹配,函数产生一个异常,并自己catch它。然后试图手工寻找DATA标识,如果找到了,一切OK,如果找不到,sorry,文件可能不完整。

ReadDATA()是三个负责读取文件头,并效验所有的标识的私有函数之一。它们组织在ReadWave函数中:

正如你所看到,我们开始的时候读取RIFF块,如果一切顺利,我们移动到下一块(移动dataAddress指针)。不要忘记我们使用内存映射文件,

操作磁盘文件就像操作内存数据一样,酷吧?然后,我们读取FMT和DATA块,当我们离开程序,指针指向声音数据。我使用一个类型定义:

typedef short          AudioWord;

typedef unsigned char  AudioByte;

现在是时候使用简单的例子来检验我们的CWaveFile了。让我们打开一个WAVE文件,然后从中读出所有声音信息。

这是一个简单的控制台程序,但它做了大量的工作。在我的计算机,我得到下面的结果:

  Format: 1

  Samples per second: 22050

  Channels: 1

  Bits per sample: 16

  Samples number: 174680

首先,是检测"noise.wav"的有效性,如果一切顺利,接着进行下面的工作。(注意格式描述=1;这是最简单的未经压缩的PCM格式。你也很容易使用CWaveFile操作其它格式的文件,但是你必须自己关心数据怎样去解释。)Samples per second : 22050. Channels: 1 = mono sound.

Bits per sample: 16。我想知道它们是因为我需要利用它们去解释声音数据。我使用reinterpret_cast转换由GetData()返回的LPVOID。这里最重要的部分是数据的长度。

你必须使用上面的代码去获取数据大小的信息;dataSIZE包含数据大小的字节数,但我们知道当前处理的是16位的声音。

因此我们将dataSIZE除以sizeof(AudioData)(或者简单地除以2,16位是两字节)。你可能只想要声音数据,我只是将它输出到控制台。

显示数据:

毫无疑问,程序运行良好,然而它只是一个控制台程序。现在是使用windows GUI的时候了。这些数据看起来像什么?现在,我们将回答这个问题。

正如你所看到的,CWaveFile从CObject继承,是因为我想在MFC中使用它。

CWaveFile有成员函数:

BOOL DrawData( CDC *pDC, RECT *pRect, CSize *pNewSize )

这个函数负责在设备上下文中描绘数据。为了你能更好地使用它,我准备了如下例子:

DrawData函数有三个参数:指向CDC的指针、指向CRect的指针,指向CSize的指针。你可以在OnSize中加入下面的代码:

就是这样。但我忘记告诉你DrawData方法的缺点。它只正确显示单声道数据。立体声和单声道不同,立体声样本是左右声道交替的(左、右、左、右...)。

最后:

我想在这多谢所有读到这里的人。我知道CWaveFile还未完成;如果你在这上面添加了一些函数,或者有什么Bug,请让我知道。网上见。

by:Alexander Beletsky 2003.2.3

from:codeGuru

翻译:snoopy

环境:VC6 SP4, VC.NET

 

注:转载文章需注明来源:VCer.net 文章地址:http://vcer.net/1076417661436.html

  如果你觉得VCer.net不错,而且你愿意为VCer.net捐赠一元钱,那么点击后面的捐赠按钮吧:) vcer.net捐赠

[回复该贴] [加入个人书签]
[投票结果]

A: 评分 10 100% (9 票)
B: 评分 5 0% (0 票)
C: 评分 0 0% (0 票)
D: 评分 -5 0% (0 票)
E: 评分 -10 0% (0 票)

 


re:snoopy每日一译-CWaveFile--一个操作和表示WAV数据的类

...很好的文章,正在学习中。谢谢博主。

hyn126 于 2008-08-20 16:40:38.0 编辑 [回复该贴]