一、综述
RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个
字节便是“RIFF”。
Chunk, Format Chunk, Fact Chunk(可选), Data Chunk。具体见下图:
------------------------------------------------
|
|
|
------------------------------------------------
|
|
------------------------------------------------
|
|
------------------------------------------------
|
|
------------------------------------------------
于Chunk最开始位置,作为标示,而且均为4个字节。并且紧跟在ID后面的是Chunk大
小(去除ID和Size所占的字节数后剩下的其他字节数目),4个字节表示,低字节
表示数值低位,高字节表示数值高位。下面具体介绍各个Chunk内容。
PS:
二、具体介绍
RIFF WAVE Chunk
和Size所占用的字节数,即FileLen - 8 = Size。然后是Type字段,为'WAVE',表
示是wav文件。
struct RIFF_HEADER
{
};
Format Chunk
则最后多了2个字节的附加信息。主要由一些软件制成的wav格式中含有该2个字节的
附加信息。
struct WAVE_FORMAT
{
};
struct FMT_BLOCK
{
};
补充头文件样例说明:
首先是一串“52 49 46 46”这个是Ascii字符“RIFF”,这部分是固定格式,表明这是一个WAVE文件头。
然后是“E4 3C 00 00”,这个是我这个WAV文件的数据大小,记住这个大小是包括头文件的一部分的,包括除了前面8个字节的所有字节,也就等于文件总字节数减去8。这是一个DWORD,我这个文件对应是15588。
然后是“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”,我们一般用的是这个。
随后的是“01 00”,这是一个WORD,对应数字1,表示声道数为1,这是个单声道Wav。
随后的是“22 56 00 00”,这是一个DWORD,对应数字22050,代表的是采样频率22050。
随后的是“44 AC 00 00”,这是一个DWORD,对应数字44100,代表的是每秒的数据量。
然后是“02 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文件的数据体了,头文件的解析就到这里。
Fact Chunk
struct FACT_BLOCK
{
};
Data Chunk
数据的大小。紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,
wav数据的bit位置可以分成以下几种形式:
{
};
FormatTag:说明
#define WAVE_FORMAT_UNKNOWN 0x0000
#define WAVE_FORMAT_PCM 0x0001
#define WAVE_FORMAT_ADPCM 0x0002
#define WAVE_FORMAT_ALAW 0x0006
#define WAVE_FORMAT_MULAW 0x0007
#define WAVE_FORMAT_GSM610 0x0031
#define WAVE_FORMAT_MPEG 0x0050
三、小结
这里具体的代码就不给出了。
四、参考资料
下面是另一篇文章的转载和摘抄
这是JRTPLIB@Conference系列的第六部《G.711编码事例程序》,本系列的主要工作是实现一个基于JRTPLIB的,建立在RTP组播基础上的多媒体视频会议系统。这只是一个实验系统,用于学习JRTPLIB、RTP、和多媒体相关的编程,不是一个完善的软件工程。而且,我只会在业余的时间出于兴趣写一写。有志同道合的朋友可以通过tinnal@136.com这个邮箱或博客回复(推荐)和我交流。
上一部《JRTPLIB@Conference DIY视频会议系统 五、PCM 和G.711编码相关》
这一部我们来做个实验,就是把用windows录音机录下来的"PCM 8.000 kHz, 16 位, 单声道"WAV文件转换成为我们要用的8位8000Hz a-law格式PCM。要注意的是录音机默认的方式是PCM 44.100 kHz, 16 位, 立体声,我们不想去进行采样频率的更改,因为这个要进行插值,而且也没必要,因为我们写软件时采样频率我们是可以更改的。所以我们要先把录音另为"PCM 8.000 kHz, 16 位, 单声道"格式。
代码上的实现
根据上面的格式规定,我们把它写成一头文件wav.h
#ifndef _WAV_H_
#define _WAV_H_
#include "types.h"
#pragma pack(1)
struct RIFF_HEADER
{
U8 szRiffID[4]; // 'R','I','F','F'
U32 dwRiffSize;
U8 szRiffFormat[4]; // 'W','A','V','E'
};
struct WAVE_FORMAT
{
U16 wFormatTag;
U16 wChannels;
U32 dwSamplesPerSec;
U32 dwAvgBytesPerSec;
U16 wBlockAlign;
U16 wBitsPerSample;
U16 pack; //附加信息
};
struct FMT_BLOCK
{
U8 szFmtID[4]; // 'f','m','t',' '
U32 dwFmtSize;
struct WAVE_FORMAT wavFormat;
};
struct FACT_BLOCK
{
U8 szFactID[4]; // 'f','a','c','t'
U32 dwFactSize;
};
struct DATA_BLOCK
{
U8 szDataID[4]; // 'd','a','t','a'
U32 dwDataSize;
};
#endif
因为这是个简单的程序,我没有去规划,相就的WAV解码过程我放到main.c的main函数里做了,这是不应该的,请原谅
/*******************************************************
* 这是配合我的博客《JRTPLIB@Conference DIY视频会议系统》
* 而写的一个阶段性实验。
* 作者:冯富秋 tinnal
* 邮箱:tinnal@163.com
* 博客:www.cnitblog.com/tinnal/
* 目期:2009-01-03
* 版本:1.00
*********************************************************/
#include "stdio.h"
#include "string.h"
#include "types.h"
#include "g711.h"
#include "wav.h"
struct RIFF_HEADER riff_header;
struct FMT_BLOCK fmt_block;
char fack_block_buffer[20]; //20 should be enough
struct FACT_BLOCK fact_block;
struct DATA_BLOCK data_block;
int main(int argc, char **argv)
{
FILE *wav_in;
FILE *wav_out;
U32 i;
U8 has_fact_block =0;
unsigned char pcm_bytes[2];
short pcm;
unsigned char a_law;
long file_pos;
if(argc != 3 )
{
printf("Usage:\n\t%s <intput file> <output file>\n", argv[0]);
exit(-1);
}
wav_in = fopen(argv[1],"rb");
if(wav_in == NULL)
{
printf("Can't open input file %s\n", argv[1]);
return (-1);
}
wav_out = fopen(argv[2], "wb");
if( wav_out == NULL)
{
printf("Can't open output file %s\n",argv[2]);
fclose(wav_in);
return(-1);
}
file_pos = ftell(wav_in);
//Read RIFF_HEADER
fread(&riff_header, sizeof(struct RIFF_HEADER), 1, wav_in);
if( memcmp(riff_header.szRiffID, "RIFF", 4) != 0 ||
memcmp(riff_header.szRiffFormat, "WAVE", 4) != 0 )
{
printf("No a vaild wave file!\n");
fclose(wav_in);
fclose(wav_out);
return(-1);
}
file_pos = ftell(wav_in);
//Read FMT_BLOCK
fread(&fmt_block, sizeof(struct FMT_BLOCK), 1, wav_in);
if( memcmp(fmt_block.szFmtID, "fmt ", 4) !=0 ||
fmt_block.dwFmtSize != 18 ||
fmt_block.wavFormat.wFormatTag != 0x1 ||
fmt_block.wavFormat.wChannels != 0x1 ||
fmt_block.wavFormat.dwSamplesPerSec != 8000 ||
fmt_block.wavFormat.wBitsPerSample != 16)
{
printf("Sorry this is only test program,\n"
"we only support follow format,\n"
"\t 1. Format: linear PCM \n"
"\t 2. Samples Rate: 8000 KHz \n"
"\t 3. Channels: one channel \n"
"\t 4. BitsPerSample: 16 \n");
fclose(wav_in);
fclose(wav_out);
return(-1);
}
file_pos = ftell(wav_in);
//Try to read FACT_BLOCK
file_pos = ftell(wav_in);
fread(&fact_block, sizeof(struct FACT_BLOCK), 1, wav_in);
if( memcmp(fact_block.szFactID, "fact", 4) == 0 )
{
has_fact_block =1;
fread(&fack_block_buffer, fact_block.dwFactSize, 1, wav_in);
}
else
fseek(wav_in, file_pos, SEEK_SET);
fread(&data_block, sizeof(struct DATA_BLOCK), 1, wav_in);
if (memcmp(data_block.szDataID, "data", 4) != 0)
{
printf("OOh what error?\n");
fclose(wav_in);
fclose(wav_out);
return(-1);
}
//Change the wave header to write
riff_header.dwRiffSize -= data_block.dwDataSize/2 ;
fmt_block.wavFormat.wFormatTag = 0x06;
fmt_block.wavFormat.wChannels = 0x01;
fmt_block.wavFormat.dwSamplesPerSec = 8000;
fmt_block.wavFormat.dwAvgBytesPerSec = 8000;
fmt_block.wavFormat.wBlockAlign = 0x01;
fmt_block.wavFormat.wBitsPerSample = 0x08;
data_block.dwDataSize -= data_block.dwDataSize/2 ;
//Write wave file header
fwrite(&riff_header, sizeof(struct RIFF_HEADER), 1, wav_out);
fwrite(&fmt_block, sizeof(struct FMT_BLOCK), 1, wav_out);
if(has_fact_block == 1)
{
fwrite(&fact_block, sizeof(struct FACT_BLOCK), 1, wav_out);
fwrite(&fack_block_buffer, fact_block.dwFactSize, 1, wav_out);
}
fwrite(&data_block, sizeof(struct DATA_BLOCK), 1, wav_out);
//Convert pcm data to a-low data and write wav file.
for(i =0; i< data_block.dwDataSize; i++)
{
pcm_bytes[0] = (U8) fgetc(wav_in);
pcm_bytes[1] = (U8) fgetc(wav_in);
pcm = *(short *)&pcm_bytes;
a_law = ALawEncode((int)pcm);
// a_law = linear2alaw((int)pcm);
fputc(a_law, wav_out);
}
fclose(wav_in);
fclose(wav_out);
printf("Finish!\n");
return 0;
}
整个文件基本都是在为WAV文件格式服务而非我们的核心工作--G.711编码。唉~,我也不想。这里在面进行G.711编码的就是ALawEncode函数。这个函数定义在g711.c里件里,这个文件函数一些我认为比较有用的函数。我们这是只把ALawEncode这个函数拿出来。
<span style="font-family:KaiTi_GB2312;">//省略的代码
unsigned char ALawEncode(int pcm16)
{
int p = pcm16;
unsigned a; // A-law value we are forming
if(p<0)
{
// -ve value
// Note, ones compliment is used here as this keeps encoding symetrical
// and equal spaced around zero cross-over, (it also matches the standard).
p = ~p;
a = 0x00; // sign = 0
}
else
{
// +ve value
a = 0x80; // sign = 1
}
// Calculate segment and interval numbers
p >>= 4;
if(p>=0x20)
{
if(p>=0x100)
{
p >>= 4;
a += 0x40;
}
if(p>=0x40)
{
p >>= 2;
a += 0x20;
}
if(p>=0x20)
{
p >>= 1;
a += 0x10;
}
}
// a&0x70 now holds segment value and 'p' the interval number
a += p; // a now equal to encoded A-law value
return a^0x55; // A-law has alternate bits inverted for transmission
}
//省略的代码
</span>