wav 文件格式解析

时间:2021-12-24 19:43:11

和 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

数据

 

                              图一、块的结构示意图

辨别码由4ASCII码所构成,数据大小则标示出紧跟其后数据的长度(单位为Byte),而数据大小本身也用掉4Byte,所以事实上一个chunk的长度为数据大小加8。一般而言,chunk本身并不允许内部再包含chunk,但有两种例外,分别为以"RIFF""L1ST"为辨别码的chunk。而针对此两种chunkRIFF又从原先的"数据"中切出4Byte4Byte称为"格式辨别码",然而RIFF又规定文件中仅能有一个以"RIFF"为辨别码的chunk

 

RIFF/LIST标志符

数据1大小

数据1

格式/列表类型

数据

 

                               图二、RIFF/LIST块结构

只要依循此一结构的文件,我们均称之为RIFF档。此种结构提供了一种系统化的分类。如果和MSDOS文件系统作比较,"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

1WAVE_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。具体见下图:

wav 文件格式解析

    1   Wav格式包含Chunk示例

其中除了Fact Chunk外,其他三个Chunk是必须的。每个Chunk有各自的ID,位
Chunk最开始位置,作为标示,而且均为4个字节。并且紧跟在ID后面的是Chunk
小(去除IDSize所占的字节数后剩下的其他字节数目),4个字节表示,低字节
表示数值低位,高字节表示数值高位。下面具体介绍各个Chunk内容。
PS

   
所有数值表示均为低字节表示低位,高字节表示高位。

二、具体介绍
RIFF WAVE Chunk

wav 文件格式解析

  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

wav 文件格式解析

'fmt '作为标示。一般情况下Size16,此时最后附加信息没有;如果为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;
 };

补充头文件样例说明:

wav 文件格式解析wav 文件格式解析

首先是一串“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定义,首先就是一个WAVEFORMATstruct
随后是“10 00 00 00”,这是一个DWORD,对应数字16,这个对应定义中的Sizeof(PCMWAVEFORMAT),后面我们可以看到这个段内容正好是16个字节。
随后的字节是“01 00”,这是一个WORD,对应定义为编码格式“WAVE_FORMAT_PCM”,我们一般用的是这个。
随后的是“02 00”,这是一个WORD,对应数字1,表示声道数为12表示双声道,这是个双声道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文件的数据体了,头文件的解析就到这里。

wav 文件格式解析

4  Fact Chunk

    Fact Chunk是可选字段,一般当wav文件由某些软件转化而成,则包含该Chunk
   
结构定义如下:
 struct FACT_BLOCK
 {
  char  szFactID[4]; // 'f','a','c','t'
  DWORD  dwFactSize;
 };

 

Data Chunk

wav 文件格式解析wav 文件格式解析

5 Data Chunk

    Data Chunk是真正保存wav数据的地方,以'data'作为该Chunk的标示。然后是
数据的大小。紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,
wav
数据的bit位置可以分成以下几种形式:

wav 文件格式解析

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}