前言:无论是文字,图像还是声音,都必须以一种特定的格式组织和存储起来,这样才能让显示器或播放器知道以怎样的一种方式去解析这些数据。
把PCM格式的数据存储成WAV格式数据的思路:先写头部,再写数据块。
WAV格式可以分成两个部分:
1.文件头,存储一些重要的参数信息,比如采样率,声道数,量化精度等等。
2.数据块,原始的PCM数据。
想要了解WAV格式的可以点击这里
下面是WAV文件结构图
我们需要简单来说明一下这张图的结构:
可以分成三个部分:
第一部分RIFF : ChunkID 存储了“RIFF”字段,表示这是一个“RIFF”格式的文件。
ChunkSize 记录整个wav文件的字节数。
Format 存储了“WAVE”字段,表示这是一个wav文件。
第二部分fmt: 这部分的内容主要是记录一些关键参数,比如采样率,声道数,量化精度等等。
Subchunk1 ID 存储了“fmt”字段
Subchunk1 Size 存储“fmt”字段的长度
AudioFormat 存储 量化精度
Num Channels 存储声道数
SampleRate 存储采样率
ByteRate 存储比特率 SampleRate * NumChannels * BitsPerSample/8
BlockAlign == NumChannels * BitsPerSample/8
BitsPerSample 8 bits = 8, 16 bits = 16, etc.
第三部分data : 主要描述数据块
Subchunk2 ID 存储“data”字段
Subchunk2Size 记录存储的二进制原始音频数据的长度
data 存储二进制原始音频数据
我在网上找了一段wav写入头部的代码,亲测成功
-
byte[] header = new byte[44];
-
//RIFF WAVE Chunk
-
// RIFF标记占据四个字节
-
header[0] = 'R';
-
header[1] = 'I';
-
header[2] = 'F';
-
header[3] = 'F';
-
//数据大小表示,由于原始数据为long型,通过四次计算得到长度
-
header[4] = (byte) (totalDataLen & 0xff);
-
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
-
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
-
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
-
//WAVE标记占据四个字节
-
header[8] = 'W';
-
header[9] = 'A';
-
header[10] = 'V';
-
header[11] = 'E';
-
//FMT Chunk
-
header[12] = 'f';
-
// 'fmt '标记符占据四个字节
-
header[13] = 'm';
-
header[14] = 't';
-
header[15] = ' ';//过渡字节
-
//数据大小
-
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
-
header[17] = 0;
-
header[18] = 0;
-
header[19] = 0;
-
//编码方式 10H为PCM编码格式
-
header[20] = 1; // format = 1
-
header[21] = 0;
-
//通道数
-
header[22] = (byte) channels;
-
header[23] = 0;
-
//采样率,每个通道的播放速度
-
header[24] = (byte) (longSampleRate & 0xff);
-
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
-
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
-
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
-
//音频数据传送速率,采样率*通道数*采样深度/8
-
header[28] = (byte) (byteRate & 0xff);
-
header[29] = (byte) ((byteRate >> 8) & 0xff);
-
header[30] = (byte) ((byteRate >> 16) & 0xff);
-
header[31] = (byte) ((byteRate >> 24) & 0xff);
-
// 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
-
header[32] = (byte) (1 * 16 / 8);
-
header[33] = 0;
-
//每个样本的数据位数
-
header[34] = 16;
-
header[35] = 0;
-
//Data chunk
-
header[36] = 'd';//data标记符
-
header[37] = 'a';
-
header[38] = 't';
-
header[39] = 'a';
-
//数据长度
-
header[40] = (byte) (totalAudioLen & 0xff);
-
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
-
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
-
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
-
out.write(header, 0, 44);
解决了最困难的点,下面的工作就好实现了。
下面是完成的代码:
-
private void writeWav() {
-
fileTarget = new File(file, "audiotest.pcm");
-
fileWav = new File(file, "audiotest.wav");
-
-
if (!fileTarget.exists()) {
-
Log.e("tag", "目标文件不存在");
-
return;
-
}
-
DataInputStream dataInputStream = null;
-
DataOutputStream dataOutputStream = null;
-
try {
-
dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(fileTarget)));
-
dataOutputStream = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(fileWav)));
-
int len = dataInputStream.available();
-
long totalAudioLen = 0;
-
long totalDataLen = totalAudioLen + 36;
-
long longSampleRate = 44100;
-
int channels = 1;
-
long byteRate = 16 * longSampleRate * channels / 8;
-
//写wav头部
-
writeWavHeader(dataOutputStream, totalAudioLen, totalDataLen, longSampleRate, channels, byteRate);
-
byte[] bytes = new byte[bufferSize];
-
int lenthg = -1;
-
while ((lenthg = dataInputStream.read(bytes)) != -1) {
-
dataOutputStream.write(bytes, 0, lenthg);
-
}
-
} catch (FileNotFoundException e) {
-
e.printStackTrace();
-
} catch (IOException e) {
-
e.printStackTrace();
-
} finally {
-
try {
-
dataInputStream.close();
-
dataOutputStream.close();
-
} catch (IOException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
private byte[] writeWavHeader(DataOutputStream dataOutputStream, long totalAudioLen, long totalDataLen, long longSampleRate,
-
int channels, long byteRate) throws IOException {
-
byte[] header = new byte[44];
-
//RIFF WAVE Chunk
-
// RIFF标记占据四个字节
-
header[0] = 'R';
-
header[1] = 'I';
-
header[2] = 'F';
-
header[3] = 'F';
-
//数据大小表示,由于原始数据为long型,通过四次计算得到长度
-
header[4] = (byte) (totalDataLen & 0xff);
-
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
-
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
-
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
-
//WAVE标记占据四个字节
-
header[8] = 'W';
-
header[9] = 'A';
-
header[10] = 'V';
-
header[11] = 'E';
-
//FMT Chunk
-
header[12] = 'f';
-
// 'fmt '标记符占据四个字节
-
header[13] = 'm';
-
header[14] = 't';
-
header[15] = ' ';//过渡字节
-
//数据大小
-
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
-
header[17] = 0;
-
header[18] = 0;
-
header[19] = 0;
-
//编码方式 10H为PCM编码格式
-
header[20] = 1; // format = 1
-
header[21] = 0;
-
//通道数
-
header[22] = (byte) channels;
-
header[23] = 0;
-
//采样率,每个通道的播放速度
-
header[24] = (byte) (longSampleRate & 0xff);
-
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
-
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
-
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
-
//音频数据传送速率,采样率*通道数*采样深度/8
-
header[28] = (byte) (byteRate & 0xff);
-
header[29] = (byte) ((byteRate >> 8) & 0xff);
-
header[30] = (byte) ((byteRate >> 16) & 0xff);
-
header[31] = (byte) ((byteRate >> 24) & 0xff);
-
// 确定系统一次要处理多少个这样字节的数据,确定缓冲区,通道数*采样位数
-
header[32] = (byte) (1 * 16 / 8);
-
header[33] = 0;
-
//每个样本的数据位数
-
header[34] = 16;
-
header[35] = 0;
-
//Data chunk
-
header[36] = 'd';//data标记符
-
header[37] = 'a';
-
header[38] = 't';
-
header[39] = 'a';
-
//数据长度
-
header[40] = (byte) (totalAudioLen & 0xff);
-
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
-
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
-
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
-
dataOutputStream.write(header, 0, 44);
-
return header;
-
}
站在巨人的肩膀上
请多多指点
接下来一篇文章我会将带来 如何解析WAV格式文件