wav文件头(Microsoft格式)

返回
Author Avatar
钢翼
2021-02-01
编程
65

最近用java调用sapi.SpVoice文本转语音。为了直接输出内存流而不是音频文件,使用了Sapi.SpMemoryStream。 可能有人会说从音频文件里面读取字节流不就行了,但是这样的话就多了文件管理(创建文件避免重名、事后删除文件),还得处理多线程问题,而且多了IO操作,效率更低。
和Sapi.SpFileStream输出的音频文件相比,Sapi.SpMemoryStream获取的字节流并没有包含wav头信息,所以专门上网研究了下。
用了网上很多wav文件头的生成代码,生成总是有问题,所以最直观的方法就是比对和Sapi.SpFileStream生成文件的文件头。

文件头长度为44,当pcm音频数据长度大于0xffff,则为46。

  • 1.audioDataLen为Sapi.SpMemoryStream对象GetData的字节数组长度。
  • 2.channels 为声道数,这里传1
  • 3.sampleRate 采样率,这里传22050
  • 4.bitsPerSample 采样精度,这里传16
  • 5.最后将文件头字节数组和GetData的字节数组拼接起来就是一个wav音频文件了。
//ckid:4字节 RIFF 标志,大写  

    wavHeader[0]  = 'R';  

    wavHeader[1]  = 'I';  

    wavHeader[2]  = 'F';  

    wavHeader[3]  = 'F';  

  

    //cksize:4字节文件长度,这个长度不包括"RIFF"标志(4字节)和文件长度本身所占字节(4字节),即该长度=整个文件长度 - 8 =  audioDataLen +(44 or 46) -8
//这里文件长度并不是什么重要的问题,错了也不影响播放。

    wavHeader[4]  = (byte)(totalDataLen & 0xff);  

    wavHeader[5]  = (byte)((totalDataLen >> 8) & 0xff);  

    wavHeader[6]  = (byte)((totalDataLen >> 16) & 0xff);  

    wavHeader[7]  = (byte)((totalDataLen >> 24) & 0xff);  

  

    //fcc type:4字节 "WAVE" 类型块标识, 大写  

    wavHeader[8]  = 'W';  

    wavHeader[9]  = 'A';  

    wavHeader[10] = 'V';  

    wavHeader[11] = 'E';  

  

    //ckid:4字节 表示"fmt" chunk的开始,此块中包括文件内部格式信息,小写, 最后一个字符是空格  

    wavHeader[12] = 'f';  

    wavHeader[13] = 'm';  

    wavHeader[14] = 't';  

    wavHeader[15] = ' ';  

  

  //cksize:4字节,文件内部格式信息数据的大小,过滤字节(Microsoft为00000012H)  

   wavHeader[16] = 0x12;  

    wavHeader[17] = 0;  

    wavHeader[18] = 0;  

    wavHeader[19] = 0;  

  

    //FormatTag:2字节,音频数据的编码方式,1:表示是PCM 编码  

    wavHeader[20] = 1;  

    wavHeader[21] = 0;  

  

    //Channels:2字节,声道数,单声道为1,双声道为2  

    wavHeader[22] = (byte) channels;  

    wavHeader[23] = 0;  

  

    //SamplesPerSec:4字节,采样率,如44100  

    wavHeader[24] = (byte)(sampleRate & 0xff);  

    wavHeader[25] = (byte)((sampleRate >> 8) & 0xff);  

    wavHeader[26] = (byte)((sampleRate >> 16) & 0xff);  

    wavHeader[27] = (byte)((sampleRate >> 24) & 0xff);  

  

    //BytesPerSec:4字节,音频数据传送速率, 单位是字节。其值为采样率×每次采样大小。播放软件利用此值可以估计缓冲区的大小;  

    //bytePerSecond = sampleRate * (bitsPerSample / 8) * channels  

    wavHeader[28] = (byte)(bytePerSecond & 0xff);  

    wavHeader[29] = (byte)((bytePerSecond >> 8) & 0xff);  

    wavHeader[30] = (byte)((bytePerSecond >> 16) & 0xff);  

    wavHeader[31] = (byte)((bytePerSecond >> 24) & 0xff);  

  

    //BlockAlign:2字节,每次采样的大小 = 采样精度*声道数/8(单位是字节); 这也是字节对齐的最小单位, 譬如 16bit 立体声在这里的值是 4 字节。  

    //播放软件需要一次处理多个该值大小的字节数据,以便将其值用于缓冲区的调整  

    wavHeader[32] = (byte)(bitsPerSample * channels / 8);  

    wavHeader[33] = 0;  

  

    //BitsPerSample:2字节,每个声道的采样精度; 譬如 16bit 在这里的值就是16。如果有多个声道,则每个声道的采样精度大小都一样的;  

   wavHeader[34] = (byte) bitsPerSample;  

    wavHeader[35] = 0;  

  //这里和网上的不一样,网上的36开始就是data了,这里需要插入两个字节,后面顺延
   wavHeader[36] = 0;
   wavHeader[37] = 0;

    //ckid:4字节,数据标志符(data),表示 "data" chunk的开始。此块中包含音频数据,小写;  

    wavHeader[38] = 'd';  

    wavHeader[39] = 'a';  

    wavHeader[40] = 't';  

    wavHeader[41] = 'a';  

  

    //cksize:音频数据的长度,4字节,audioDataLen 

    wavHeader[42] = (byte)(audioDataLen & 0xff);  

    wavHeader[43] = (byte)((audioDataLen >> 8) & 0xff);  

	if (audioDataLen > 0xffff) {

      wavHeader[44] = (byte)((audioDataLen >> 16) & 0xff);  

      wavHeader[45] = (byte)((audioDataLen >> 24) & 0xff); 
	}