WAV格式遵循RIFF格式
由于我们需要的是它的第二层的fmt
块和data
块
并且WAV文件前12字节的内容是RIFF
<文件大小-8>
WAVE
所以我们可以跳过它们进行读取
引入相关的库
#include <stdio.h>
#include <string.h>
定义块
结构体
typedef struct _CHUNK_{
long start; // 当前块的起始位置(输入用)
char id[4]; // 块的ID
unsigned int size; // 块的数据大小
void* buffer; // 块的数据(输出用)
} CHUNK,*rCHUNK;
以及…fmt
块
typedef struct _FMT_{
// 音频格式(PCM是1,IEEE float是3,0xFFFE必须在扩展快内指定音频格式)
unsigned short AudioFormat;
// 声道数
unsigned short NumChannels;
// 采样率
unsigned int SampleRate;
// 每秒字节数
unsigned int ByteRate;
// 块大小(单次采样的总字节数)
unsigned short BlockAlign;
// 每个采样的比特数
unsigned short BitPerSample;
// 扩展快大小(一般是22)
unsigned short cbSize;
unsigned short VBPS;
unsigned int ChannelMask;
unsigned char GUID[16];
} FMT,*rFMT;
读取FMT
int ReadFMT(rCHUNK ck,rFMT fmt,FILE* fp){
if(!ck || !fmt) return -1;
fseek(fp,ck->start+8,SEEK_SET);
fread(&fmt->AudioFormat,2,1,fp);
fread(&fmt->NumChannels,2,1,fp);
fread(&fmt->SampleRate,4,1,fp);
fread(&fmt->ByteRate,4,1,fp);
fread(&fmt->BlockAlign,2,1,fp);
fread(&fmt->BitPerSample,2,1,fp);
// 读取扩展快
if(ck->size > 16){
fread(&fmt->cbSize,2,1,fp);
fread(&fmt->VBPS,2,1,fp);
fread(&fmt->ChannelMask,4,1,fp);
fread(fmt->GUID,16,1,fp);
}
// 重新计算部分参数
fmt->BlockAlign = fmt->BitPerSample*fmt->NumChannels;
fmt->ByteRate = fmt->BlockAlign*fmt->SampleRate;
return 0;
}
初始化块
int InitChunk(rCHUNK ck,long start,char* id,unsigned int size,void* data){
if (!ck || !id) return -1;
ck->start = start;
memcpy(ck->id,id,4);
ck->size = size;
ck->buffer = data;
return 0;
}
初始化FMT(不考虑扩展块)
int InitFMT(rFMT fmt,unsigned short AF,unsigned short NC,unsigned int SR,unsigned short BPS){
if(!fmt) return -1;
fmt->AudioFormat = AF;
fmt->NumChannels = NC;
fmt->SampleRate = SR;
fmt->BitPerSample = BPS;
fmt->BlockAlign = BPS * NC / 8;
fmt->ByteRate = fmt->BlockAlign * fmt->SampleRate;
return 0;
}
上面这是FMT块的数据
现在要初始化FMT块
int InitFMTChunk(rCHUNK ck,rFMT fmt,unsigned int fmt_size){
if(!ck || !fmt) return -1;
InitChunk(ck,12,"fmt ",fmt_size,fmt);
return 0;
}
读取一个块
int LoadChunk(rCHUNK now,rCHUNK next,FILE* fp){
if(!now||!next||!fp)return -1;
// 定位到当前块
if(fseek(fp,now->start,SEEK_SET)) return 1;
// 获取下一个块的起始位置
next->start = now->start + 8 + now->size;
// 跳转到下一块
if(fseek(fp,next->start,SEEK_SET)) return 1;
// 读取块信息
if(!fread((next->id),4,1,fp)) return 2;
if(!fread(&(next->size),4,1,fp)) return 2;
return 0;
}
WAV文件RIFF块下的子块里面不仅有fmt和data块
还有其他的比如fact块和LIST块
所以我们要循环读取并比较块的ID
int LoadWAV(FILE* fp,rCHUNK fmt,rCHUNK data){
CHUNK start,tmp;
InitChunk(&start,0,"RIFF",4,NULL);
// 循环读取
LoadChunk(&start,&tmp,fp);
do{
if(!memcmp(tmp.id,"fmt ",4)){
InitChunk(fmt,tmp.start,tmp.id,tmp.size,NULL);
}else
if(!memcmp(tmp.id,"data",4)){
InitChunk(data,tmp.start,tmp.id,tmp.size,NULL);
}
}while(!LoadChunk(&tmp,&tmp,fp));
return 0;
}
最后是保存WAV文件
int SaveWAV(FILE* fp,rCHUNK fmt,rCHUNK data){
fwrite("RIFF",4,1,fp);
fwrite("\0\0\0\0",4,1,fp);
fwrite("WAVE",4,1,fp);
fwrite(fmt->id,4,1,fp);
fwrite(&fmt->size,4,1,fp);
fwrite(fmt->buffer,fmt->size,1,fp);
fwrite(data->id,4,1,fp);
fwrite(&data->size,4,1,fp);
fwrite(data->buffer,data->size,1,fp);
unsigned int riff_size = ftell(fp) - 8;
fseek(fp,4,SEEK_SET);
fwrite(&riff_size,4,1,fp);
return 0;
}
非常简单
这篇文章就结束了,记得给仓库点一个star,谢谢