相关知识,仅供参考:
一、WAV文件介绍
在Windows环境下,大部分多媒体文件都是按照资源互换文件格式(Resources lnterchange File Format)存放信息,简称RIFF格式。构成RIFF文件的基本单位称之为块(chunk),每个RIFF文档是由若干个块构成。每个块(chunk)由块标识、块长度及数据等三部分所组成。
1.1 WAV文件结构
RIFF是英文Resource Interchange File Format的缩写,每个WAVE文件的头四个字节便是“RIFF”。
WAVE文件是由若干个Chunk组成的,按照在文件中的出现位置包括:
RIFF WAVE Chunk 块 |
Format Chunk 块 |
Fact Chunk 块(压缩编码格式要含有该块)(可选) |
Data Chunk 块 |
RIFF格式规定,只有RIFF及LIST块可以含有子块,其它的块不允许包含子块。
WAV文件采用的是RIFF格式结构,至少是由3个块构成,分别是RIFFChunk、fmt Chunk和Data Chunk。所有基于压缩编码的WAV文件必须含有fact块。
1.2 RIFF WAVE Chunk结构
字段 | 字节数 | 具体内容 |说明 |
ID | 4 Bytes | RIFF | 作为标示 |
Size | 4 Bytes | 具体数 | 该size是整个wav文件大小减去ID和Size所占用的字节数,即FileLen - 8 = Size |
Type | 4 Bytes | WAVE | 表示是wav文件 |
以RIFF作为标示,然后紧跟着为size字段,该size是整个wav文件大小减去ID和Size所占用的字节数,即FileLen - 8 = Size。然后是Type字段,为WAVE表示是wav文件
1.3 Format Chunk 块
字段 | 字节数 | 具体内容 |说明 |
ID | 4 Bytes | fmt | |
Size | 4 Bytes | 数值为16或18,18则最后又附加信息 | |
FormatTag | 2 Bytes | 编码方式,一般为0x0001 | | |
Channels | 2 Bytes | 声道数目,1--单声道;2--双声道 | | |
SamplesPerSec | 4 Bytes | 采样频率 | | |
AvgBytesPerSec| 4 Bytes | 每秒所需字节数 | |===> WAVE_FORMAT |
BlockAlign | 2 Bytes | 数据块对齐单位(每个采样需要的字节数) | | |
BitsPerSample | 2 Bytes | 每个采样需要的bit数 | | |
可选 | 2 Bytes | 附加信息(可选,通过Size来判断有无) | | |
以fmt作为标示,一般情况下Size为16,此时最后附加信息没有;如果为18则最后多了2个字节的附加信息。主要由一些软件制成的wav格式中含有该2个字节的
1.3 Fact Chunk 块
字段 | 字节数 | 具体内容 |说明 |
ID | 4 Bytes | fact | |
Size | 4 Bytes | 数值为4 | |
data | 4 Bytes | | |
Fact Chunk是可选字段,一般当wav文件由某些软件转化而成,则包含该Chunk。
1.4 Data Chunk 块
字段 | 字节数 | 具体内容 |说明 |
ID | 4 Bytes | data | |
Size | 4 Bytes | 数据长度| |
data | | | |
Data Chunk是真正保存wav数据的地方,以'data'作为该Chunk的标示。然后是数据的大小,紧接着就是wav数据。
1.5 wav数据的bit位置
根据Format Chunk中的声道数以及采样bit数,wav数据的bit位置可以分成以下几种形式:
| 单声道 | 取样1 | 取样2 | 取样3 | 取样4 |
| |--------------------------------------------------------
| 8bit量化 | 声道0 | 声道0 | 声道0 | 声道0 |
---------------------------------------------------------------------
| 双声道 | 取样1 | 取样2 |
| |--------------------------------------------------------
| 8bit量化 | 声道0(左) | 声道1(右) | 声道0(左) | 声道1(右) |
---------------------------------------------------------------------
| | 取样1 | 取样2 |
| 单声道 |--------------------------------------------------------
| 16bit量化 | 声道0 | 声道0 | 声道0 | 声道0 |
| | (低位字节) | (高位字节) | (低位字节) | (高位字节) |
---------------------------------------------------------------------
| | 取样1 |
| 双声道 |--------------------------------------------------------
| 16bit量化 | 声道0(左) | 声道0(左) | 声道1(右) | 声道1(右) |
| | (低位字节) | (高位字节) | (低位字节) | (高位字节) |
---------------------------------------------------------------------
1.6 补充说明
采样频率:每秒钟采集音频数据的次数。采样频率越高,音频保真度越高。计算机广泛配置的16位声卡,使用的采样频率通常包括11025Hz、22050Hz、44100Hz和48000Hz四种,其中,采用11025Hz采样的声音效果相当于电话声音的效果;采用22050HZ采样的声音效果相当于FM调频广
播的效果;采用44100HZ采样的声音效果相当于CD声音的效果。
采样位数(振幅采样精度):即采样值或取样值,是用来衡量声音波动变化的一个参数,也是声卡的分辨率。它的数值越大,分辨率也就越高,发出声音的能力越强。目前计算机中配置的16位声卡的采样位数包括8位和16位两种。
声道数:有单声道和立体声之分,单声道的声音只能使用一个喇叭发声(有的声卡也将单声道信息处理成两个喇叭同时输出),立体声的WAV可以使两个喇叭都发声(一般左右声道有分工),这样更能感受到音频信息的空间效果。显然,双声道数据还原特性更接近人们的听力习惯,但采集得到的数据量会增加1倍。
二、读出WAV文件头部(Java代码)
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* 读取wav文件表头
* @author 赵海洋
* @date 2024-9-16
* @des 主要应用与ASR语音识别过程中,部分wav文件采样率与模型的不匹配,导致语音识别错误
* */
public class WavReaderHeader {
private String filePath;//文件路径
private int numChannels;//声道
private int sampleRate;//采样率
private int bitsPerSample;//比特率,每秒的字节数
private int numSamples;
private byte[] audioData;
public WavReaderHeader(String filePath) throws IOException {
this.filePath = filePath;
//readWavFile();
}
public void readWavFile() throws IOException {
RandomAccessFile reader = new RandomAccessFile(new File(filePath), "r");
byte[] buffer = new byte[2048];
int bytesRead =-1;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
while ((bytesRead = reader.read(buffer)) != -1) {
outputStream.write(buffer,0,bytesRead);
}
outputStream.flush();
byte[] binaryData = outputStream.toByteArray();
ByteBuffer byteBuffer = ByteBuffer.wrap(binaryData);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
// Read RIFF header
byte[] riffHeader = new byte[4];
byteBuffer.get(riffHeader);
if (!new String(riffHeader).equals("RIFF")) {
throw new IllegalArgumentException("Invalid WAV file");
}
// System.out.println("RIFF");
// Read file size
int fileSize = byteBuffer.getInt();
// System.out.println("fileSize="+fileSize);
// Read WAVE header
byte[] waveHeader = new byte[4];
byteBuffer.get(waveHeader);
if (!new String(waveHeader).equals("WAVE")) {
throw new IllegalArgumentException("Invalid WAV file");
}
// System.out.println("WAV file");
// Read FMT chunk
byte[] fmtChunk = new byte[4];
byteBuffer.get(fmtChunk);
if (!new String(fmtChunk).equals("fmt ")) {
throw new IllegalArgumentException("Invalid WAV file");
}
// Read FMT chunk size
int fmtChunkSize = byteBuffer.getInt();
// System.out.println("fmtChunkSize="+fmtChunkSize);
// Read audio format
int audioFormat = byteBuffer.getShort();
if (audioFormat != 1) {
throw new IllegalArgumentException("Invalid WAV file");
}
// Read number of channels
numChannels = byteBuffer.getShort();
System.out.println("numChannels="+numChannels);
// Read sample rate
sampleRate = byteBuffer.getInt();
System.out.println("sampleRate="+sampleRate);
// Read byte rate
int byteRate = byteBuffer.getInt();
System.out.println("byteRate="+byteRate);
// Read block align
int blockAlign = byteBuffer.getShort();
// System.out.println("blockAlign="+blockAlign);
// Read bits per sample
bitsPerSample = byteBuffer.getShort();
System.out.println("bitsPerSample="+bitsPerSample);
// Read DATA chunk
byte[] dataChunk = new byte[4];
byteBuffer.get(dataChunk);
if (!new String(dataChunk).equals("data")) {
throw new IllegalArgumentException("Invalid WAV file");
}
// Read data chunk size
int dataChunkSize = byteBuffer.getInt();
// System.out.println("dataChunkSize="+dataChunkSize);
// Read audio data
audioData = new byte[dataChunkSize];
byteBuffer.get(audioData);
numSamples = dataChunkSize / (numChannels * bitsPerSample / 8);
System.out.println("numSamples="+numSamples);
}
public int getNumChannels() {
return numChannels;
}
public int getSampleRate() {
return sampleRate;
}
public int getBitsPerSample() {
return bitsPerSample;
}
public int getNumSamples() {
return numSamples;
}
public byte[] getAudioData() {
return audioData;
}
public static void main(String[]args){
try{
new WavReaderHeader("E:\\54dc.wav").readWavFile();
}catch (IOException e){
e.printStackTrace();
}
}
}
如果wav文件有头部信息,则需要把头部信息读出来,看一下wav头部信息与模型信息是否一致,否则需要对wav文件头部信息做转化处理。如果是模型流输出文件,是没有头部信息的,如果要保存到本地被播器识别,需要补充增加头部信息。