和 bmp 文件格式原理是一样的,在此不再赘述。贴出转载链接,供大家分享,以便记录开发的点滴,希望老了有所回忆——“我当程序员的那些年”。
http://mcljc.blog.163.com/blog/static/83949820107256393872/
http://www.sieye.cn/showArticle.asp?nameID=20113121442060
http://blog.chinaunix.net/uid-21977330-id-3976817.html
http://www.it9000.cn/tech/CTI/wav.html
http://blog.csdn.net/bluesoal/article/details/932395
贴出以上链接中的某一个我认为比较好的:
WAV文件格式分析
一. RIFF概念
在Windows环境下,大部分的多媒体文件都依循着一种结构来存放信息,这种结构称为"资源互换文件格式"(Resources lnterchange File Format),简称RIFF。例如声音的WAV文件、视频的AV1文件等等均是由此结构衍生出来的。RIFF可以看做是一种树状结构,其基本构成单位为chunk,犹如树状结构中的节点,每个chunk由"辨别码"、"数据大小"及"数据"所组成。
块的标志符(4BYTES) |
数据大小(4BYTES) |
数据 |
图一、块的结构示意图
辨别码由4个ASCII码所构成,数据大小则标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4个Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF"及"L1ST"为辨别码的chunk。而针对此两种chunk,RIFF又从原先的"数据"中切出4个Byte。此4个Byte称为"格式辨别码",然而RIFF又规定文件中仅能有一个以"RIFF"为辨别码的chunk。
RIFF/LIST标志符 |
|
数据1大小 |
|
数据1 |
格式/列表类型 |
数据 |
图二、RIFF/LIST块结构
只要依循此一结构的文件,我们均称之为RIFF档。此种结构提供了一种系统化的分类。如果和MS一DOS文件系统作比较,"RIFF"chunk就好比是一台硬盘的根目录,其格式辨别码便是此硬盘的逻辑代码(C:或D:),而"L1ST"chunk即为其下的子目录,其他的chunk则为一般的文件。至于在RIFF文件的处理方面,微软提供了相关的函数。视窗下的各种多媒体文件格式就如同在磁盘机下规定仅能放怎样的目录,而在该目录下仅能放何种数据。
二. WAV文件格式
WAVE文件是非常简单的一种RIFF文件,它的格式类型为"WAVE"。RIFF块包含两个子块,这两个子块的ID分别是"fmt"和"data",其中"fmt"子块由结构PCMWAVEFORMAT所组成,其子块的大小就是sizeofof(PCMWAVEFORMAT),数据组成就是PCMWAVEFORMAT结构中的数据。
标志符(RIFF) |
数据大小 |
格式类型("WAVE") |
"fmt" |
Sizeof(PCMWAVEFORMAT) |
PCMWAVEFORMAT |
"data" |
声音数据大小 |
声音数据 |
图三、WAVE文件结构
PCMWAVEFORMAT结构定义如下:
Typedef struct
...{
WAVEFORMAT wf; /波形格式;
WORD wBitsPerSample; //WAVE文件的采样大小;
} PCMWAVEFORMAT;
//WAVEFORMAT结构定义如下:
typedef struct
...{
WORD wFormatag; //编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORD nChannls; //声道数,单声道为1,双声道为2;
DWORD nSamplesPerSec; //采样频率;
DWORD nAvgBytesperSec; //每秒的数据量;
WORD nBlockAlign; //块对齐;
} WAVEFORMAT;
"data"子块包含WAVE文件的数字化波形声音数据,其存放格式依赖于"fmt"子块中wFormatTag成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。如16bit的单声道WAVE文件和双声道WAVE文件的数据采样格式分别如图四所示:
16位单声道:
采样一 |
采样二 |
…… |
||
低字节 |
高字节 |
低字节 |
高字节 |
…… |
16位双声道:
采样一 |
…… |
|||
左声道 |
右声道 |
…… |
||
低字节 |
高字节 |
低字节 |
高字节 |
…… |
图四、WAVE文件数据采样格式
WAV文件格式实例分析:
0 1 2 3 4 5 6 7 8 9 A B C D E F | |
00000000H 00000010H 00000020H 00000030H 00000040H |
52 49 46 46 0A 06 01 00 57 41 56 45 66 6D 74 20 12 00 00 00 01 00 02 00 44 AC 00 00 10 B1 02 00 04 00 10 00 00 00 66 61 63 74 04 00 00 00 76 41 00 00 64 61 74 61 D8 05 01 00 00 00 00 00 FF FF 00 00 FE FF FE FF 00 00 00 00 FE FF FE FF 00 00 |
|
偏移地址 |
字节数 |
数据类型 |
内容 |
文件头 |
00H |
4 |
char |
“RIFF”; RIFF标志 |
04H |
4 |
long int |
0x00 01 06 0A(注意数据存储顺序); 文件长度 |
|
08H |
4 |
char |
“WAVE”; WAVE标志 |
|
0CH |
4 |
char |
“fmt ”; fmt标志,最后一位为空 |
|
10H |
4 |
long int |
0x12; sizeof(PCMWAVEFORMAT) |
|
14H |
2 |
int |
1(WAVE_FORMAT_PCM); 格式类别,1表示为PCM形式的声音数据 |
|
16H |
2 |
int |
2; 通道数,单声道为1,双声道为2 |
|
18H |
2 |
int |
44100; 采样频率(每秒样本数) |
|
1CH |
4 |
long int |
0x10B10000; 每秒数据量;其值为通道数×每秒数据位数×每样本的数据位数/8。播放软件利用此值可以估计缓冲区的大小。 |
|
20H |
2 |
int |
数据块的调整数(按字节算的),其值为通道数×每样本的数据位值/8。播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整。 |
|
22H |
2 |
|
每样本的数据位数,表示每个声道中各个样本的数据位数。如果有多个声道,对每个声道而言,样本大小都一样。 |
|
50H |
4 |
char |
“data”; 数据标记符 |
|
54H |
4 |
long int |
0x00 01 05 D8; 语音数据大小 |
注意:初次研究,有不对的地方请各位提出。另外,24H-50H间的数据令我感到困惑,借此请教各位,先谢过了。
一、综述
WAVE文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。
RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个
字节便是“RIFF”。
WAVE文件是由若干个Chunk组成的。按照在文件中的出现位置包括:RIFF WAVE
Chunk, Format Chunk, Fact Chunk(可选), Data Chunk。具体见下图:
图1 Wav格式包含Chunk示例
其中除了Fact Chunk外,其他三个Chunk是必须的。每个Chunk有各自的ID,位
于Chunk最开始位置,作为标示,而且均为4个字节。并且紧跟在ID后面的是Chunk大
小(去除ID和Size所占的字节数后剩下的其他字节数目),4个字节表示,低字节
表示数值低位,高字节表示数值高位。下面具体介绍各个Chunk内容。
PS:
所有数值表示均为低字节表示低位,高字节表示高位。
二、具体介绍
RIFF WAVE Chunk
图2 RIFF WAVE Chunk
以'FIFF'作为标示,然后紧跟着为size字段,该size是整个wav文件大小减去ID
和Size所占用的字节数,即FileLen - 8 = Size。然后是Type字段,为'WAVE',表
示是wav文件。
结构定义如下:
struct RIFF_HEADER
{
char szRiffID[4]; // 'R','I','F','F'
DWORD dwRiffSize;
char szRiffFormat[4]; // 'W','A','V','E'
};
Format Chunk
以'fmt '作为标示。一般情况下Size为16,此时最后附加信息没有;如果为18
则最后多了2个字节的附加信息。主要由一些软件制成的wav格式中含有该2个字节的
附加信息。
结构定义如下:
struct WAVE_FORMAT
{
WORD wFormatTag;
WORD wChannels;
DWORD dwSamplesPerSec;
DWORD dwAvgBytesPerSec;
WORD wBlockAlign;
WORD wBitsPerSample;
};
struct FMT_BLOCK
{
char szFmtID[4]; // 'f','m','t',' '
DWORD dwFmtSize;
WAVE_FORMAT wavFormat;
};
补充头文件样例说明:
首先是一串“52 49 46 46”这个是Ascii字符“RIFF”,这部分是固定格式,表明这是一个WAVE文件头。
然后是“3ec1 0400”,这个是我这个WAV文件的数据大小,记住这个大小是包括头文件的一部分的,包括除了前面8个字节的所有字节,也就等于文件总字节数减去8。这是一个DWORD,我这个文件对应是311614+8 = 311622。
然后是“57 41 56 45 66 6D 74 20”,也是Ascii字符“WAVEfmt”,这部分是固定格式。
然后是PCMWAVEFORMAT部分,可以对照一下上面的struct定义,首先就是一个WAVEFORMAT的struct。
随后是“10 00 00 00”,这是一个DWORD,对应数字16,这个对应定义中的Sizeof(PCMWAVEFORMAT),后面我们可以看到这个段内容正好是16个字节。
随后的字节是“01 00”,这是一个WORD,对应定义为编码格式“WAVE_FORMAT_PCM”,我们一般用的是这个。
随后的是“02 00”,这是一个WORD,对应数字1,表示声道数为1,2表示双声道,这是个双声道Wav。
随后的是“44 AC 00 00”,这是一个DWORD,对应数字44100,代表的是采样率。
随后的是“22 56 00 00”,这是一个DWORD,对应数字22050,代表的是采样频率22050。
然后是“04 00”,这是一个WORD,对应数字是2,表示块对齐的内容,每个采样对应的字节数。
然后是“10 00”,这是一个WORD,对应WAVE文件的采样大小,数值为16,采样位数16Bits。
然后是一串“64 61 74 61”,这个是Ascii字符“data”,标示头结束,开始数据区域。
而 后是数据区的开头,有一个DWORD,我这里的字符是“C0 3C 00 00”,对应的十进制数为15552,看一下前面正好可以看到,文件大小是15596,其中到“data”标志出现为止的头是40个字节,再减去这个标志 的4个字节正好是15552,再往后面就是真正的Wave文件的数据体了,头文件的解析就到这里。
图4 Fact Chunk
Fact Chunk是可选字段,一般当wav文件由某些软件转化而成,则包含该Chunk。
结构定义如下:
struct FACT_BLOCK
{
char szFactID[4]; // 'f','a','c','t'
DWORD dwFactSize;
};
Data Chunk
图5 Data Chunk
Data Chunk是真正保存wav数据的地方,以'data'作为该Chunk的标示。然后是
数据的大小。紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,
wav数据的bit位置可以分成以下几种形式:
图6 wav数据bit位置安排方式
Data Chunk头结构定义如下:
struct DATA_BLOCK
{
char szDataID[4]; // 'd','a','t','a'
DWORD dwDataSize;
};
三、小结
因此,根据上述结构定义以及格式介绍,很容易编写相应的wav格式解析代码。
这里具体的代码就不给出了。
编解码可能要用到的
二、代码的实现
根据上面的格式规定,我们把它写成一头文件wav.h
1#ifndef _WAV_H_
2#define _WAV_H_
3
4 #include "types.h"
5
6 #pragma pack(1)
7
8struct RIFF_HEADER
9{
10 U8 szRiffID[4]; // 'R','I','F','F'
11 U32 dwRiffSize;
12 U8 szRiffFormat[4]; // 'W','A','V','E'
13};
14
15struct WAVE_FORMAT
16{
17 U16 wFormatTag;
18 U16 wChannels;
19 U32 dwSamplesPerSec;
20 U32 dwAvgBytesPerSec;
21 U16 wBlockAlign;
22 U16 wBitsPerSample;
23 U16 pack; //附加信息
24};
25struct FMT_BLOCK
26{
27 U8 szFmtID[4]; // 'f','m','t',' '
28 U32 dwFmtSize;
29 struct WAVE_FORMAT wavFormat;
30 };
31
32struct FACT_BLOCK
33{
34 U8 szFactID[4]; // 'f','a','c','t'
35 U32 dwFactSize;
36 };
37
38struct DATA_BLOCK
39{
40 U8 szDataID[4]; // 'd','a','t','a'
41 U32 dwDataSize;
42 };
43
44
45#endif
因为这是个简单的程序,我没有去规划,相就的WAV解码过程我放到main.c的main函数里做了,这是不应该的,请原谅
1/**//*******************************************************
2 * 这是配合我的博客《JRTPLIB@Conference DIY视频会议系统》
3 * 而写的一个阶段性实验。
4 * 作者:冯富秋 tinnal
5 * 邮箱:tinnal@163.com
6 * 博客:www.cnitblog.com/tinnal/
7 * 目期:2009-01-03
8 * 版本:1.00
9 *********************************************************/
10
11 #include "stdio.h"
12 #include "string.h"
13 #include "types.h"
14 #include "g711.h"
15 #include "wav.h"
16
17struct RIFF_HEADER riff_header;
18struct FMT_BLOCK fmt_block;
19char fack_block_buffer[20]; //20 should be enough
20struct FACT_BLOCK fact_block;
21struct DATA_BLOCK data_block;
22
23int main(int argc, char **argv)
24{
25 FILE *wav_in;
26 FILE *wav_out;
27 U32 i;
28 U8 has_fact_block =0;
29
30 unsigned char pcm_bytes[2];
31 short pcm;
32 unsigned char a_law;
33
34 long file_pos;
35
36 if(argc != 3 )
37 {
38 printf("Usage:\n\t%s <intput file> <output file>\n", argv[0]);
39 exit(-1);
40 }
41
42 wav_in = fopen(argv[1],"rb");
43 if(wav_in == NULL)
44 {
45 printf("Can't open input file %s\n", argv[1]);
46 return (-1);
47 }
48
49 wav_out = fopen(argv[2], "wb");
50 if( wav_out == NULL)
51 {
52 printf("Can't open output file %s\n",argv[2]);
53 fclose(wav_in);
54 return(-1);
55 }
56
57 file_pos = ftell(wav_in);
58
59 //Read RIFF_HEADER
60 fread(&riff_header, sizeof(struct RIFF_HEADER), 1, wav_in);
61 if( memcmp(riff_header.szRiffID, "RIFF", 4) != 0 ||
62 memcmp(riff_header.szRiffFormat, "WAVE", 4) != 0 )
63 {
64 printf("No a vaild wave file!\n");
65 fclose(wav_in);
66 fclose(wav_out);
67 return(-1);
68 }
69 file_pos = ftell(wav_in);
70
71 //Read FMT_BLOCK
72 fread(&fmt_block, sizeof(struct FMT_BLOCK), 1, wav_in);
73 if( memcmp(fmt_block.szFmtID, "fmt ", 4) !=0 ||
74 fmt_block.dwFmtSize != 18 ||
75 fmt_block.wavFormat.wFormatTag != 0x1 ||
76 fmt_block.wavFormat.wChannels != 0x1 ||
77 fmt_block.wavFormat.dwSamplesPerSec != 8000 ||
78 fmt_block.wavFormat.wBitsPerSample != 16)
79 {
80 printf("Sorry this is only test program,\n"
81 "we only support follow format,\n"
82 "\t 1. Format: linear PCM \n"
83 "\t 2. Samples Rate: 8000 KHz \n"
84 "\t 3. Channels: one channel \n"
85 "\t 4. BitsPerSample: 16 \n");
86 fclose(wav_in);
87 fclose(wav_out);
88 return(-1);
89 }
90
91 file_pos = ftell(wav_in);
92
93 //Try to read FACT_BLOCK
94 file_pos = ftell(wav_in);
95 fread(&fact_block, sizeof(struct FACT_BLOCK), 1, wav_in);
96 if( memcmp(fact_block.szFactID, "fact", 4) == 0 )
97 {
98 has_fact_block =1;
99 fread(&fack_block_buffer, fact_block.dwFactSize, 1, wav_in);
100 }
101 else
102 fseek(wav_in, file_pos, SEEK_SET);
103
104 fread(&data_block, sizeof(struct DATA_BLOCK), 1, wav_in);
105 if (memcmp(data_block.szDataID, "data", 4) != 0)
106 {
107 printf("OOh what error?\n");
108 fclose(wav_in);
109 fclose(wav_out);
110 return(-1);
111 }
112
113 //Change the wave header to write
114 riff_header.dwRiffSize -= data_block.dwDataSize/2 ;
115
116 fmt_block.wavFormat.wFormatTag = 0x06;
117 fmt_block.wavFormat.wChannels = 0x01;
118 fmt_block.wavFormat.dwSamplesPerSec = 8000;
119 fmt_block.wavFormat.dwAvgBytesPerSec = 8000;
120 fmt_block.wavFormat.wBlockAlign = 0x01;
121 fmt_block.wavFormat.wBitsPerSample = 0x08;
122
123 data_block.dwDataSize -= data_block.dwDataSize/2 ;
124
125 //Write wave file header
126 fwrite(&riff_header, sizeof(struct RIFF_HEADER), 1, wav_out);
127 fwrite(&fmt_block, sizeof(struct FMT_BLOCK), 1, wav_out);
128 if(has_fact_block == 1)
129 {
130 fwrite(&fact_block, sizeof(struct FACT_BLOCK), 1, wav_out);
131 fwrite(&fack_block_buffer, fact_block.dwFactSize, 1, wav_out);
132 }
133 fwrite(&data_block, sizeof(struct DATA_BLOCK), 1, wav_out);
134
135 //Convert pcm data to a-low data and write wav file.
136 for(i =0; i< data_block.dwDataSize; i++)
137 {
138 pcm_bytes[0] = (U8) fgetc(wav_in);
139 pcm_bytes[1] = (U8) fgetc(wav_in);
140 pcm = *(short *)&pcm_bytes;
141
142 a_law = ALawEncode((int)pcm);
143// a_law = linear2alaw((int)pcm);
144 fputc(a_law, wav_out);
145 }
146 fclose(wav_in);
147 fclose(wav_out);
148
149 printf("Finish!\n");
150 return 0;
151 }
152
整个文件基本都是在为WAV文件格式服务而非我们的核心工作--G.711编码。唉~,我也不想。这里在面进行G.711编码的就是ALawEncode函数。这个函数定义在g711.c里件里,这个文件函数一些我认为比较有用的函数。我们这是只把ALawEncode这个函数拿出来。
1//省略的代码
2unsigned char ALawEncode(int pcm16)
3{
4 int p = pcm16;
5 unsigned a; // A-law value we are forming
6 if(p<0)
7 {
8 // -ve value
9 // Note, ones compliment is used here as this keeps encoding symetrical
10 // and equal spaced around zero cross-over, (it also matches the standard).
11 p = ~p;
12 a = 0x00; // sign = 0
13 }
14 else
15 {
16 // +ve value
17 a = 0x80; // sign = 1
18 }
19
20 // Calculate segment and interval numbers
21 p >>= 4;
22 if(p>=0x20)
23 {
24 if(p>=0x100)
25 {
26 p >>= 4;
27 a += 0x40;
28 }
29 if(p>=0x40)
30 {
31 p >>= 2;
32 a += 0x20;
33 }
34 if(p>=0x20)
35 {
36 p >>= 1;
37 a += 0x10;
38 }
39 }
40 // a&0x70 now holds segment value and 'p' the interval number
41
42 a += p; // a now equal to encoded A-law value
43
44 return a^0x55; // A-law has alternate bits inverted for transmission
45}