一、概述:
  本文叙述了如何通过IMA-ADPCM压缩和解压缩算法来完成从IMA-ADPCM文件转换为PCM文件的过程。主要包括的内容有:PCMIMA-ADPCM WAVE文件内部结构的介绍,IMA-ADPCM压缩与解压缩算法,以及如何生成特有的音频压缩格式文件等三方面的内容。
二、WAVE文件的认识
  WAVE文件是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav"
wave文件有很多不同的压缩格式,而且现在一些程序生成的wave文件都或多或少地含有一些错误。这些错误的产生不是因为单个数据压缩和解压缩算法的问题,而是因为在压缩和解压缩后没有正确地组织好文件的内部结构。所以,正确而详细地了解各种WAVE文件的内部结构是成功完成压缩和解压缩的基础,也是生成特有音频压缩格式文件的前提。
最基本的WAVE文件是PCM(脉冲编码调制)格式的,这种文件直接存储采样的声音数据没有经过任何的压缩,是声卡直接支持的数据格式,要让声卡正确播放其它被压缩的声音数据,就应该先把压缩的数据解压缩成PCM格式,然后再让声卡来播放。
1.Wave文件的内部结构
        WAVE文件是以RIFF(Resource Interchange File Format,"资源交互文件格式")格式来组织内部结构的。RIFF文件结构可以看作是树状结构,其基本构成是称为""Chunk)的单元,最顶端是一个RIFF”块,下面的每个块有类型块标识(可选)”、标志符数据大小数据等项所组成,块的结构如表1所示:
      上面说到的类型块标识只在部分chunk中用到,如WAVEchunk中,这时表示下面嵌套有别的chunk,当使用了类型块标识时,该chunk就没有别的项(如块标志符,数据大小等),它只作为文件读取时的一个标识。先到这个类型块标识,再以它为起来读取它下面嵌套的其它chunk
每个文件最前端写入的是RIFF块,每个文件只有一个RIFF块。从表2中可以看出它的结构:
PCM格式的文件会至少多加入一个“fact”块,它用来记录数据解压缩后的大小。(注意是数据而不是文件)这个“fact”块一般加在“data”块的前面。
2.WAVEFORMAT结构的认识
PCM和非PCM的主要区别是声音数据的组织不同,这些区别可以通过两者的WAVEFORMAT结构来区分。下面以PCMIMA-ADPCM来进行对比:
WAVE的基本结构WAVEFORMATEX结构定义如下:
typedef struct
{
WORD wFormatag;  //编码格式,包括WAVE_FORMAT_PCM//WAVEFORMAT_ADPCM
WORD  nChannls;      //声道数,单声道为1,双声道为2;
DWORD nSamplesPerSec;//采样频率;
DWORD nAvgBytesperSec//每秒的数据量;
WORD  nBlockAlign;//块对齐;
WORD  wBitsPerSample;//WAVE文件的采样大小;
WORD  sbSize;        //PCM中忽略此值字符串截取后面三位
}WAVEFORMATEX
PCM的结构就是基本结构;
 IMAADPCMWAVEFORMAT结构定义如下:
Typedef struct
{
    WAVEFORMATEX wfmt;
    WORD nSamplesPerBlock;
}IMAADPCMWAVEFORMAT;
 IMA-ADPCMwfmt->cbsize不能忽略,一般取值为2,表示此类型的WAVEFORMAT比一般的WAVEFORMAT多出2个字节。这两个字符也就是nSamplesPerBlock
 3.factchunk的内部组织 
在非PCM格式的文件中,一般会在WAVEFORMAT结构后面加入一个“factchunk,结构如下:
typedef struct{
    char[4];          //fact”字符串
    DWORD chunksize;
    DWORD datafactsize;    //数据转换为PCM格式后的大小。
}factchunk;
datafactsize是这个chunk中最重要的数据,如果这是某种压缩格式的声音文件,那么从这里就可以知道他解压缩后的大小。对于解压时的计算会有很大的好处!
 4.“datachunk的内部组织
从“datachunk的第9个字节开始,存储的就是声音信息的数据了,(前八个字节存储的是标志符“data”和后接数据大小size(DWORD)。这些数据可能是压缩的,也可能是没有压缩的。
PCM中的声音数据没有被压缩,如果是单声道的文件,采样数据按时间的先后顺序依次存入。(它的基本组织单位是BYTE(8bit)WORD(16bit))如果是双声道的文件,采样数据按时间先后顺序交叉地存入。如图所示:
IMA-ADPCM是压缩格式,它是从PCM16位采样压缩成4位的。对于单声道的IMA-ADPCM来说,它是将PCM的数据按时间次序依次压缩并写入文件中的,每个byte中含两个采样,低四位对应第一个采样,高四位对应第二个采样。而对于双声道的IMA-ADPCM来说,它的存储相对就麻烦一些了,它是将PCM的左声道的前8个采样依次压缩并写入到一个DWORD中,
然后写入“datachunk里。紧接着是右声道的前8个采样。以此循环,当采样数不足8时(到数据尾端),应该把多出来的采样用0填充。其示意图如下:
特别注意:
IMA-ADPCM中,“datachuck中的数据是以block形式来组织的,我把它叫做“段”,也就是说在进行压缩时,并不是依次把所有的数据进行压缩保存,而是分段进行的,这样有一个十
分重要的好处:那就是在只需要文件中的某一段信息时,可以在解压缩时可以只解所需数据所在的段就行了,没有必要再从文件开始起一个一个地解压缩。这对于处理大文件将有相当的优势。同时,这样也可以保证声音效果。
Block一般是由block header (block) data 两者组成的。其中block header是一个结构,它在单声道下的定义如下:
Typedef struct
{
    short  sample0;    //block中第一个采样值(未压缩)
    BYTE  index;    //上一个block最后一个index,第一个blockindex=0;
    BYTE  reserved;  //尚未使用
}MonoBlockHeader;
有了blockheader的信息后,就可以不需要知道这个block前面和后面的数据而轻松地解出本block中的压缩数据。对于双声道,它的blockheader应该包含两个MonoBlockHeader其定义如下:
typedaf struct
{
    MonoBlockHeader leftbher;
    MonoBlockHeader rightbher;
}StereoBlockHeader;
在解压缩时,左右声道是分开处理的,所以必须有两个MonoBlockHeader;
1:上述的index是解压缩算法中必须用到的一个参数。详见后面。
2: 关于block的大小,通常会有以下几种情况:
    对于单声道,大小一般为512byte,显然这里面可以保存的sample个数为(512-sizeof(MonoBlockHeader))/4 + 1 = 1017<其中"+1"是第一个存在头结构中的没有压缩的sample.
    对于双声道,大小一般为1024byte,按上面的算法可以得出,其中的sample个数也是1017.
4.读取WAVE文件的方法.
在知道了WAVE文件的内部数据组织后,可以直接通过FILEHFILE来实现文件的读取。但由于WAVE文件是以RIFF格式来组织的,所以用多媒体输入输出流来操作将更加方便,可以直接在文件中查chunk并定位数据。
三、IMA-ADPCM 编码和解码算法
    IMA-ADPCM Intel公司首先开发的是一种主要针对16bit采样波形数据的有损压缩算法, 压缩比为 4:1.它与通常的DVI-ADPCM是同一算法。 (8bit数据压缩时是3.2:1,也有非标准的IMA-ADPCM压缩算法,可以达到5:1甚至更高的压缩比)4:1的压缩是目前使用最多的压缩方式。
  ADPCM(Adaptive Differential Pulse Code Modulation 差分脉冲编码调制)主要是针对连续的波形数据的, 保存的是相临波形的变化情况, 以达到描述整个波形的目的。算法中必须用到两个一维数组,setptab[] index_adjust[],附在下面的代码之后。
 
--------------------------------------------------------------------------------
IMA-ADPCM 压缩过程 
首先我们认为声音信号都是从零开始的,那么需要初始化两个变量
int index = 0,prev_sample = 0;
但在实际使用中,prev_sample的值是每个block中第一个采样的值。(这点在后面的block中会详细介绍)
假设已经写好了两个函数:
        GetNextSamp() ——得到一个 16bit 的采样数据;
        SaveComCode() ——保存一个 4bit 的压缩样品;
下面的循环将依次压缩声音数据流: 
while (还有数据要处理) {
    cur_sample = GetNextSamp();        // 得到PCM中的当前采样数据
    diff = cur_sample-prev_sample;      // 计算出和上一个的增量
    if (diff<0)
    {

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。