音频WAV详解

244 阅读2分钟

​ WAV是最常见的声音文件格式之一,是Microsoft 和 IBM 为 PC 开发的一种声音文件格式,该文件能记录各种单声道或立体声的声音信息,并能保证声音不失真。它符合资源互换文件格式(RIFF)规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持。

但WAV文件有一个致命的缺点,就是它所占用的磁盘空间太大(每分钟的音乐大约需要12兆磁盘空间)。

WAVE文件支持很多不同的比特率、采样率、多声道音频。WAVE是PC机上存储PCM音频最流行的文件格式,基本上可以等同于原始数字音频。

WAVE文件为了与IFF保持一致,数据采用“chunk”来存储。因此,如果想要在WAVE文件中补充一些新的信息,只需要在新chunk中添加信息,而不需要改变整个文件。这也是设计IFF最初的目的。WAVE文件是很多不同的chunk集合,但是对于一个基本的WAVE文件而言,以下三种chunk是必不可少的。

​编辑

 文件中第一个chunk是RIFFchunk,然后是fmtchunk,最后是datachunk。对于其他的chunk,顺序没有严格的限制。可参考下图:

​编辑

WAV 格式定义 

RIFF头chunk表示如下:

typedef struct {
    char          ChunkID[4]; //内容为"RIFF"
    unsigned long ChunkSize;  //存储文件的字节数(不包含ChunkID和ChunkSize这8个字节)
    char          Format[4];  //内容为"WAVE“
} WAVE_HEADER;

第二个块是fmt chunk,它用来描述WAVE文件的特性,例如比特率、声道数。可以使用结构体来描述fmt chunk.

typedef struct {
   char           Subchunk1ID[4]; //内容为"fmt"
   unsigned long  Subchunk1Size;  //存储该子块的字节数(不含前面的Subchunk1ID和Subchunk1Size这8个字节)
   unsigned short AudioFormat;    //存储音频文件的编码格式,例如若为PCM则其存储值为1。
   unsigned short NumChannels;    //声道数,单声道(Mono)值为1,双声道(Stereo)值为2,等等
   unsigned long  SampleRate;     //采样率,如8k,44.1k等
   unsigned long  ByteRate;       //每秒存储的bit数,其值 = SampleRate * NumChannels * BitsPerSample / 8
   unsigned short BlockAlign;     //块对齐大小,其值 = NumChannels * BitsPerSample / 8
   unsigned short BitsPerSample;  //每个采样点的bit数,一般为8,16,32等。
} WAVE_FMT;

最后,描述包含实际声音数据的data chunk:

typedef struct {
   char          Subchunk2ID[4]; //内容为“data”
   unsigned long Subchunk2Size;  //接下来的正式的数据部分的字节数,其值 = NumSamples * NumChannels * BitsPerSample / 8
} WAVE_DATA;

WAV 文件头解析

这里是一个 WAVE 文件的开头 72 字节:

52 49 46 46 | 24 08 00 00 | 57 41 56 45
66 6d 74 20 | 10 00 00 00 | 01 00 02 00 
22 56 00 00 | 88 58 01 00 | 04 00 10 00
64 61 74 61 | 00 08 00 00 | 00 00 00 00 
24 17 1E F3 | 3C 13 3C 14 | 16 F9 18 F9
34 E7 23 A6 | 3C F2 24 F2 | 11 CE 1A 0D

字段解析如下图: 

​编辑

WAV 开发实践

PCM格式转为WAV格式 

typedef struct WAVE_HEADER{  
        char         fccID[4];        
        unsigned   long    dwSize;            
        char         fccType[4];    
    }WAVE_HEADER;  
    typedef struct WAVE_FMT{  
        char         fccID[4];        
        unsigned   long       dwSize;            
        unsigned   short     wFormatTag;    
        unsigned   short     wChannels;  
        unsigned   long       dwSamplesPerSec;  
        unsigned   long       dwAvgBytesPerSec;  
        unsigned   short     wBlockAlign;  
        unsigned   short     uiBitsPerSample;  
    }WAVE_FMT;  
    typedef struct WAVE_DATA{  
        char       fccID[4];          
        unsigned long dwSize;              
    }WAVE_DATA;  

int simplest_pcm16le_to_wave(const char *pcmpath,int channels,int sample_rate,const char *wavepath)
{
   
    if(channels==0||sample_rate==0){
    channels = 2;
    sample_rate = 44100;
    }
    int bits = 16;
    WAVE_HEADER   pcmHEADER;  
    WAVE_FMT   pcmFMT;  
    WAVE_DATA   pcmDATA;  

    unsigned   short   m_pcmData;
    FILE   *fp,*fpout;  
    fp=fopen(pcmpath, "rb");
    if(fp == NULL) {  
        printf("open pcm file error\n");
        return -1;  
    }
    fpout=fopen(wavepath,   "wb+");
    if(fpout == NULL) {    
        printf("create wav file error\n");  
        return -1; 
    }        
    //WAVE_HEADER
    memcpy(pcmHEADER.fccID,"RIFF",strlen("RIFF"));                    
    memcpy(pcmHEADER.fccType,"WAVE",strlen("WAVE"));  
    fseek(fpout,sizeof(WAVE_HEADER),1); 
    //WAVE_FMT
    pcmFMT.dwSamplesPerSec=sample_rate;  
    pcmFMT.dwAvgBytesPerSec=pcmFMT.dwSamplesPerSec*sizeof(m_pcmData);  
    pcmFMT.uiBitsPerSample=bits;
    memcpy(pcmFMT.fccID,"fmt ",strlen("fmt "));  
    pcmFMT.dwSize=16;  
    pcmFMT.wBlockAlign=2;  
    pcmFMT.wChannels=channels;  
    pcmFMT.wFormatTag=1;  

    fwrite(&pcmFMT,sizeof(WAVE_FMT),1,fpout); 
    //WAVE_DATA;
    memcpy(pcmDATA.fccID,"data",strlen("data"));  
    pcmDATA.dwSize=0;
    fseek(fpout,sizeof(WAVE_DATA),SEEK_CUR);
    fread(&m_pcmData,sizeof(unsigned short),1,fp);
    while(!feof(fp)){  
        pcmDATA.dwSize+=2;
        fwrite(&m_pcmData,sizeof(unsigned short),1,fpout);
        fread(&m_pcmData,sizeof(unsigned short),1,fp);
    }  
    pcmHEADER.dwSize=44+pcmDATA.dwSize;
    rewind(fpout);
    fwrite(&pcmHEADER,sizeof(WAVE_HEADER),1,fpout);
    fseek(fpout,sizeof(WAVE_FMT),SEEK_CUR);
    fwrite(&pcmDATA,sizeof(WAVE_DATA),1,fpout);

    fclose(fp);
    fclose(fpout);
    return 0;
}

注意声道数和采样率,一般采样率有44100/16000/8000,另外声道是1还是2,这两个参数要设置好才会有正确的转换结果。  ​ 微信首发欢迎转载并关注。微信号:江湖修行。