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);
}
}
}