预备知识:

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