PCM转WAV只需在PCM文件前面增加WAV头即可,WAV文件头中包含的信息有:采样率、采样的位数、文件大小等。头文件的长度占44字节。
采样的位数指的是描述数字信号所使用的位数。8位(8bit)代表2的8次方=256,16 位(16bit)则代表2的16次方=65536 / 1024 =64K
采样率是一秒钟内对声音信号的采样次数
网络接收一个音频的时长是20ms, 已知音频采样率是8kHz,采样的位数是16bit。
[时长]20ms * [采样率]8kHz * [采样的位数]16bit = 320 byte
例如,CD碟采用16位的采样精度,44.1KHz的采样频率,为双声道,它每秒所需要的数据量为16×44100×2÷8=176400字节。这样算下来,比特率应该是1400多Kbps,如果采用MP3、WMA编码格式,比特率能够更小。
WAV文件的基本格式 |
类型 | 内容 | 变量名 | 大小 | 取值 |
RIFF头 |
文件标识符串 |
fileId |
4B |
"RIFF" |
头后文件长度 |
fileLen |
4B |
非负整数(=文件长度-8) |
数据类型标识符 |
波形文件标识符 |
waveId |
4B |
"WAVE" |
格式块 |
块头 |
格式块标识符 |
chkId |
4B |
"fmt" |
头后块长度 |
chkLen |
4B |
非负整数(=16或18) |
块数据 |
格式标记 |
wFormatTag |
2B |
非负短整数(PCM=1) |
声道数 |
wChannels |
2B |
非负短整数(=1或2) |
采样率 |
dwSampleRate |
4B |
非负整数(单声道采样数/秒) |
平均字节率 |
dwAvgBytesRate |
4B |
非负整数(字节数/秒) |
数据块对齐 |
wBlockAlign |
2B |
非负短整数(不足补零) |
采样位数 |
wBitsPerSample |
2B |
非负短整数(PCM时才有) |
扩展域大小 |
wExtSize |
2B |
非负短整数 |
可选(根据chkLen=16或18判断) |
扩展域 |
extraInfo |
extSize B |
扩展信息 |
数据块 |
块头 |
数据块标识符串 |
chkId |
4B |
"data" |
头后块长度 |
chkLen |
4B |
非负整数 |
块数据 |
波形采样数据 |
x 或 xl、xr |
chkLen B |
左右声道样本交叉排列
样本值为整数(整字节存储,不足位补零),整个数据块按blockAlign对齐 |
示例:
package com.pvcp.common.utils;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class PcmToWavUtil {
public static boolean convert(String pcmFilePath, String wavFilePath, int bitsPerSample, int samplesPerSec) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(pcmFilePath);
fos = new FileOutputStream(wavFilePath);
// 计算长度
int pcmSize = fis.available();// 由于音频文件一般不会大于 Int 最大值,所以使用 available
// 填入参数,比特率等等。这里用的是 16位 单声道 8000 hz
WavHeader header = new WavHeader();
// 长度字段 = 内容的大小(PCMSize) + 头部字段的大小(不包括前面4字节的标识符RIFF以及fileLength本身的4字节)
header.fileLength = pcmSize + (44 - 8);
header.fmtHdrLeth = 16;
header.bitsPerSample = (short)bitsPerSample;
header.channels = 1;
header.formatTag = 0x0001;
// header.samplesPerSec = 8000;
header.samplesPerSec = samplesPerSec;
header.blockAlign = (short) (header.channels * header.bitsPerSample / 8);
header.avgBytesPerSec = header.blockAlign * header.samplesPerSec;
header.dataHdrLeth = pcmSize;
byte[] h = header.getHeader();
assert h.length == 44;// WAV标准,头部应该是44字节
// write header
fos.write(h, 0, h.length);
// write data stream
SourceUtils.write(fis, fos);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
} finally {
SourceUtils.close(fis, fos);
}
}
}
package com.pvcp.common.utils;
import java.io.Closeable;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class SourceUtils {
private static Log logging = LogFactory.getLog(SourceUtils.
class);
public static void runMethod(String methodName,Object

objs){
if(objs ==
null){
return;
}
for(Object obj:objs){
if(obj ==
null){
return;
}
try {
Method m = obj.getClass().getDeclaredMethod(methodName);
m.invoke(obj);
}
catch (Exception e) {
logging.error("执行["+methodName+"]方法异常!对象:"+obj+";异常:"+e.getMessage());
}
}
}
public static void write(InputStream in,OutputStream out){
try {
byte[] buff =
new byte[1024*5];
int len = 0;
while((len=in.read(buff)) != -1){
try {
out.write(buff,0,len);
out.flush();
}
catch (Exception e) {
}
}
}
catch (Exception e) {
}
}
public static void close(Object

objs){
if(objs ==
null){
return;
}
for(Object obj:objs){
if(obj ==
null){
return;
}
try {
if(obj
instanceof Closeable){
((Closeable) obj).close();
}
else if(obj
instanceof AutoCloseable){
((AutoCloseable) obj).close();
}
else{
runMethod("close", obj);
}
}
catch (Exception e) {
logging.error("关闭资源异常!对象:"+obj+";异常:"+e.getMessage(), e);
}
}
}
}
package com.pvcp.common.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class WavHeader {
/**
* 4 资源交换文件标志(RIFF)
*/
public final char fileID[] = { 'R', 'I', 'F', 'F' };
/**
* 4 总字节数
*/
public int fileLength;
/**
* 4 WAV文件标志(WAVE)
*/
public char wavTag[] = { 'W', 'A', 'V', 'E' };
/**
* 4 波形格式标志(fmt ),最后一位空格
*/
public char fmtHdrID[] = { 'f', 'm', 't', ' ' };
/**
* 4 过滤字节(一般为00000010H),若为00000012H则说明数据头携带附加信息
*/
public int fmtHdrLeth;
/**
* 2 格式种类(值为1时,表示数据为线性PCM编码)
*/
public short formatTag;
/**
* 2 通道数,单声道为1,双声道为2
*/
public short channels;
/**
* 4 采样频率
*/
public int samplesPerSec;
/**
* 4 波形数据传输速率(每秒平均字节数)
*/
public int avgBytesPerSec;
/**
* 2 DATA数据块长度,字节
*/
public short blockAlign;
/**
* 2 PCM位宽
*/
public short bitsPerSample;
/**
* 4 数据标志符(data)
*/
public char dataHdrID[] = { 'd', 'a', 't', 'a' };
/**
* 4 DATA总数据长度字节
*/
public int dataHdrLeth;
public byte[] getHeader() throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
WriteChar(bos, fileID);
WriteInt(bos, fileLength);
WriteChar(bos, wavTag);
WriteChar(bos, fmtHdrID);
WriteInt(bos, fmtHdrLeth);
WriteShort(bos, formatTag);
WriteShort(bos, channels);
WriteInt(bos, samplesPerSec);
WriteInt(bos, avgBytesPerSec);
WriteShort(bos, blockAlign);
WriteShort(bos, bitsPerSample);
WriteChar(bos, dataHdrID);
WriteInt(bos, dataHdrLeth);
bos.flush();
byte[] r = bos.toByteArray();
bos.close();
return r;
}
private void WriteShort(ByteArrayOutputStream bos, int s) throws IOException {
byte[] mybyte = new byte[2];
mybyte[1] = (byte) ((s << 16) >> 24);
mybyte[0] = (byte) ((s << 24) >> 24);
bos.write(mybyte);
}
private void WriteInt(ByteArrayOutputStream bos, int n) throws IOException {
byte[] buf = new byte[4];
buf[3] = (byte) (n >> 24);
buf[2] = (byte) ((n << 8) >> 24);
buf[1] = (byte) ((n << 16) >> 24);
buf[0] = (byte) ((n << 24) >> 24);
bos.write(buf);
}
private void WriteChar(ByteArrayOutputStream bos, char[] id) {
for (int i = 0; i < id.length; i++) {
char c = id[i];
bos.write(c);
}
}
}