预备知识:
WAV格式说明:
http://baike.baidu.com/view/8033.htm
总体思路:分别建立与WAV格式相对应的类:
- struct RIFF_HEADER
- {
- char szRiffID[4];
- DWORD dwRiffSize;
- char szRiffFormat[4];
- };
- struct WAVE_FORMAT
- {
- WORD wFormatTag;
- WORD wChannels;
- DWORD dwSamplesPerSec;
- DWORD dwAvgBytesPerSec;
- WORD wBlockAlign;
- WORD wBitsPerSample;
- };
- struct FMT_BLOCK
- {
- char szFmtID[4];
- DWORD dwFmtSize;
- WAVE_FORMAT wavFormat;
- };
- struct FACT_BLOCK
- {
- char szFmtID[4];
- DWORD dwFactSize;
- DWORD dwFactData;
- };
- struct DATA_BLOCK
- {
- char szDataID[4]; // 'd','a','t','a'
- DWORD dwDataSize;
- };
- struct SOUND_DATA
- {
- int channel;
- int BitsPerSample;
- std::vector<unsigned short> leftChannelData;
- std::vector<unsigned short> rightChannelData;
- };
- class WaveHeader
- {
- private:
- char* fileName;
- long pos;
- SOUND_DATA sound;
- RIFF_HEADER * header;
- FMT_BLOCK * format;
- FACT_BLOCK * fact;
- DATA_BLOCK * data;
- long f; //频率
- public:
- WaveHeader(char a[],int len);
- bool init();
- void InitSoundData();
- long getFileSize();
- short getChannels();
- long getSamplesPerSec();
- long getAvgBytesPerSec();
- short getBlockAlign(); //每采样需要字节数
- short getBitsPerSample(); //每个采样的bit数
- bool isThereFact();
- long getDataSize();
- SOUND_DATA &getSoundData();
- long getF();
- };
其中最重要的结构体是WaveHeader和SOUND_DATA;
它们的作用分别是:
WaveHeader是用来保存WAV文件的头部信息,包括,采样率,采样位数,大小,等等。但不保存真正的数据。就像IP协议里的头部,与数据部分是独立的。而保存数据的是SOUND_DATA.
我们来看下SOUND_DATA的结构
- struct SOUND_DATA
- {
- int channel;
- int BitsPerSample;
- std::vector<unsigned short> leftChannelData;
- std::vector<unsigned short> rightChannelData;
- };
在这个结构体里有两个向量,数据部分就是存放在向量中。因为有的声音是用双声道来采集的,故这里用两个向量来分别表示左右声道数据,这样分开它们,有利用理解和处理。
大体的结构看完了,也就对WAV文件的读取有了大体的轮廓,下面,程序所要实现的功能其实大体和WaveHeader结构体所提供的方法有关系。
我们来分析这些功能方法和实现它们的步骤:
- class WaveHeader
- {
- private:
- char* fileName;
- //WAV文件名
- long pos;
- // 这个变量说明一下,它是用来保存WAV文件的头部分结束时的字节数,即头部分的大小的。
- // 这样可以定位数据的开始从而可以对SOUND_DATA进行初始化。
- SOUND_DATA sound;
- //保存声音数据
- RIFF_HEADER * header;
- FMT_BLOCK * format;
- FACT_BLOCK * fact;
- DATA_BLOCK * data;
- //以上4个为WAV文件头部的内容。
- long f; //频率
- public:
- WaveHeader(char a[],int len);
- bool init();
- /* {这个方法是最程序最核心的部分了,其实功能很简单,就是为对变量部分进行初始化
- 但只是对头部的内容进行了初始化。为方便显示用其它颜色字体代替,这样就不会找不到西了。
- fstream in;
- in.open(fileName,ios::binary|ios::in);
- if(!in)
- {
- cout<<"OPEN FILE FAILED!!!"<<endl;
- return false; }
- //读进结构体中,进行初始化 in.read((char*)header,sizeof(RIFF_HEADER));
- if(!(isCharsEqual(header->szRiffID,"RIFF",4)&&isCharsEqual(header->szRiffFormat,"WAVE",4)))
- {
- cout<<"NOT RIFF OR WAVE"<<endl;
- return false;
- }
- in.read((char*)format,sizeof(FMT_BLOCK));
- if(!isCharsEqual(format->szFmtID,"fmt",3))
- {
- cout<<"NOT Fmt !!"<<endl;
- return false;
- }
- //判断是否有附加信息(2Bytes)
- if(format->dwFmtSize==18)
- {
- in.get();
- in.get();
- }
- int position=in.tellg(); //记录当前指针,如果没有fact则回读
- in.read((char*)fact,sizeof(FACT_BLOCK));
- if(!isCharsEqual(fact->szFmtID,"fact",4))
- {
- cout<<endl<<"No FAct"<<endl;
- fact=NULL;
- in.seekg(position);
- }
- cout<<"before data tellg()"<<in.tellg()<<endl;
- in.read((char*)data,sizeof(DATA_BLOCK));
- if(!isCharsEqual(data->szDataID,"data",4))
- {
- cout<<"NOT DATA"<<endl;
- return false;
- }
- pos=in.tellg();
- in.close();
- return true;
- void InitSoundData();
- /* 这里只实现了单声道的8位和16位的数据的读取,而又声道也是很简单的,可以自己
- 添加里去*/
- sound.channel=format->wavFormat.wChannels;
- sound.BitsPerSample=format->wavFormat.wBitsPerSample;
- fstream in;
- in.open(fileName,ios::binary|ios::in);
- if(!in)
- cout<<"FAILED TO OPEN FILE!!!"<<endl;
- in.seekg(pos);
- unsigned char a=0,b=0;
- if(sound.channel==1&&sound.BitsPerSample==8)
- { while(!in.eof())
- { a=in.get();
- sound.leftChannelData.push_back(a);
- } }
- if(sound.channel==2&&sound.BitsPerSample==8)
- { while(!in.eof())
- {
- a=in.get();
- sound.leftChannelData.push_back(a);
- b=in.get();
- sound.leftChannelData.push_back(b);
- } }
- if(sound.channel==1&&sound.BitsPerSample==16)
- {
- unsigned short c=0;
- while(!in.eof())
- {
- a=in.get();
- b=in.get();
- //这里不得不说明,数据为16位时,WAV文件的高8位在后,低8位在前,而且是以补码的形势
- 存在,这样我们可以再还原出原码:如果为正,原码等于初码,如果为负,原码等于补码减一
- 再取反。为了自己的方便,我是把数据全部用正来表示,相当于把零线上移至32768处。
- if(b<128) //即原值为正数
- {
- c=b<<8;
- c+=a;
- c+=32767;// 因为原来是用正负来表示,现只用无符号数表示
- } else
- {
- c=b<<8;
- c+=a;
- c=c-1;
- c=~c;
- c=c&0x7fff;
- c=32767-c;
- }
- sound.leftChannelData.push_back(c);
- }
- } if(sound.channel==2&&sound.BitsPerSample==16)
- { //可以参考单声道16位数据的做法。。。
- }
- long getFileSize();
- return (8+header->dwRiffSize);
- short getChannels();
- return format->wavFormat.wChannels;
- long getSamplesPerSec();
- return format->wavFormat.dwSamplesPerSec;
- long getAvgBytesPerSec();
- return format->wavFormat.dwAvgBytesPerSec;
- short getBlockAlign(); //每采样需要字节数
- return format->wavFormat.wBlockAlign;
- short getBitsPerSample(); //每个采样的bit数
- return format->wavFormat.wBitsPerSample;
- bool isThereFact();
- if(fact) return true; return false;
- long getDataSize();
- return data->dwDataSize;
- SOUND_DATA &getSoundData();
- return sound;
- long getF();
- //频率的确定这里有两个方法,一个为确定波形过中线的次数,再除以2即为波的个数
- //另种方法是用两个值来确定 波峰和波谷的个数。从而得到波的个数
- //最后用波个数除以所用的时间,即可以得到频率。
- if(getSoundData().channel==2) return 0;
- // 方法一: 根据过中间线来确定频率
- /* double time=data->dwDataSize/format->wavFormat.dwSamplesPerSec;
- cout<<endl<<endl<<"Time is:"<<time<<endl;
- vector<unsigned short>::iterator it=getSoundData().leftChannelData.begin();
- short beftmp; while(it!=getSoundData().leftChannelData.end())
- { beftmp=*it; it++;
- if((beftmp-128*getSoundData().BitsPerSample/8)*(*it-128*getSoundData().BitsPerSample/8)<0)
- f++; } return f/time/2; */
- 方法二: //根据波峰来确定频率
- double time=(double)getDataSize()/getAvgBytesPerSec();;
- vector<unsigned short>::iterator it=getSoundData().leftChannelData.begin();
- unsigned short beftmp;
- bool flag=true;
- int deleteNum=0;
- it=getSoundData().leftChannelData.begin();
- int firstPos=0;
- while(it!=getSoundData().leftChannelData.end())
- {
- it++;
- if((32768-330<*it)&&(*it<(32768+330)))
- {*it=32768;
- firstPos+=1;
- }
- else
- {
- break;
- }
- }
- it=getSoundData().leftChannelData.begin();
- int lastPos=0; int tmpNum=0;
- while(it!=getSoundData().leftChannelData.end())
- {
- tmpNum++;
- if((32768-330>*it)||(*it>(32768+330)))
- lastPos=tmpNum;
- it++;
- }
- it=getSoundData().leftChannelData.begin();
- it+=firstPos;
- beftmp=0;
- for(int i=firstPos;i<lastPos;i++)
- {
- beftmp=*it;
- it++;
- if((*it<beftmp&&flag)||(*it>beftmp&&!flag))
- {
- f++;
- flag=!flag;
- }
- if(*it<beftmp)
- flag=true;
- else
- flag=false;
- }
- time=(double)(lastPos-firstPos)/this->getSamplesPerSec();
- return f/time/2;
- }; 附带方法,工具用:
- bool isCharsEqual(char* a,char*b,int len)
- { for(int i=0;i<len;i++)
- { if(a[i]!=b[i])
- return false;
- }
- return true;
- }
转载于:https://blog.51cto.com/conflee/306261