1跛十、簡(jiǎn)介
我們獲取到的音頻數(shù)據(jù),可能是需要存儲(chǔ)的秕硝。但是不能只存原始的數(shù)據(jù)芥映,需要一定的格式來(lái)存儲(chǔ)。比如圖片的存儲(chǔ)格式有JPEG远豺、PNG奈偏、GIF等。視頻有mp4躯护、avi惊来、RMVB等。音頻的格式有mp3棺滞、wav裁蚁、WMA等。這些文件都是可以在播放器中直接播放的继准,但是音頻錄制的時(shí)候采樣率枉证、聲道數(shù)等等這些必要的參數(shù)是需要讓播放器知道的,不然播放器就不能正常的播放音頻文件移必。所以在存儲(chǔ)的時(shí)候我們會(huì)為數(shù)據(jù)加上指定的“頭”室谚,這里面包括了音頻的采樣率、聲道等參數(shù)信息。下面我們就學(xué)習(xí)wav格式舞萄。
2眨补、wav格式的文件頭
通過(guò)參考這個(gè)網(wǎng)站:http://soundfile.sapp.org/doc/WaveFormat
文件頭包括三個(gè)部分
- 第一部分通過(guò)“ChunkID”來(lái)表示這是一個(gè) “RIFF”格式的文件管削,通過(guò)“Format”填入“WAVE”來(lái)標(biāo)識(shí)這是一個(gè) wav 文件倒脓。而“ChunkSize”則記錄了整個(gè) wav 文件的字節(jié)數(shù)。
- 第二部分屬于“fmt”信息塊含思,主要記錄了本 wav 音頻文件的詳細(xì)音頻參數(shù)信息崎弃,例如:通道數(shù)、采樣率含潘、位寬等等饲做。
- 第三部分屬于“data”信息塊,由“Subchunk2Size”這個(gè)字段來(lái)記錄后面存儲(chǔ)的二進(jìn)制原始音頻數(shù)據(jù)的長(zhǎng)度遏弱。
第一部分和第二部分義工占36個(gè)字節(jié)盆均、第三部分Subchunk2ID、和Subchunk2Size各占4字節(jié)漱逸,這44字節(jié)是文件頭固定的長(zhǎng)度泪姨。后面的字節(jié)就是真正的數(shù)據(jù)部分。所以當(dāng)我們拿到了原始PCM數(shù)據(jù)后饰抒,加入前44字節(jié)文件頭肮砾,保存為wav格式。這樣就可以在其他播放器上播放了袋坑。
3仗处、工具代碼
/**
* 原始PCM數(shù)據(jù)轉(zhuǎn)WAV
*/
public class PcmToWavUtil {
/**
* 采樣率
*/
private int mSampleRate;
/**
* 聲道數(shù)
*/
private int mChannel;
/**
* @param sampleRate sample rate、采樣率
* @param channel channel枣宫、聲道
*/
PcmToWavUtil(int sampleRate, int channel) {
this.mSampleRate = sampleRate;
this.mChannel = channel;
}
/**
* pcm文件轉(zhuǎn)wav文件
*/
public ByteArrayOutputStream pcmToWav(ByteArrayOutputStream pcmBaos) {
//音頻數(shù)據(jù)的長(zhǎng)度
long totalAudioLen;
//音頻數(shù)據(jù)的長(zhǎng)度和文件頭中36字節(jié)的總和
long totalDataLen;
long longSampleRate = mSampleRate;
int channels = mChannel == AudioFormat.CHANNEL_IN_MONO ? 1 : 2;
long byteRate = 16 * mSampleRate * channels / 8;
ByteArrayOutputStream wavBaos = null;
try {
totalAudioLen = pcmBaos.size();
//由于文件頭中婆誓,后8個(gè)字節(jié)也是屬于數(shù)據(jù)部分,所以這里只加上前面的36字節(jié)
totalDataLen = totalAudioLen + 36;
wavBaos = new ByteArrayOutputStream();
//添加頭
writeWaveFileHeader(wavBaos, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
wavBaos.write(pcmBaos.toByteArray());
wavBaos.flush();
return wavBaos;
} catch (IOException e) {
try {
pcmBaos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
if (pcmBaos != null) {
try {
pcmBaos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (wavBaos != null) {
try {
wavBaos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 加入wav文件頭
*/
private void writeWaveFileHeader(ByteArrayOutputStream wavBaos, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels, long byteRate)
throws IOException {
byte[] header = new byte[44];
// RIFF/WAVE header
header[0] = 'R';
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
//Format 'WAVE'
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
//Subchunk1ID 'fmt'
header[12] = 'f';
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
// 4 bytes: size of 'fmt ' chunk
header[16] = 16;
header[17] = 0;
header[18] = 0;
header[19] = 0;
// format = 1
header[20] = 1;
header[21] = 0;
//通道數(shù)
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);
//音頻數(shù)據(jù)攢送率
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
// block align
header[32] = (byte) (channels * 16 / 8);
header[33] = 0;
// 每個(gè)樣本的數(shù)據(jù)位數(shù)
header[34] = 16;
header[35] = 0;
//data
header[36] = 'd';
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);
wavBaos.write(header);
}
}
上面的writeWaveFileHeader()方法就可以很明確的看到前44字節(jié)的創(chuàng)建過(guò)程也颤。
如果我們需要讀取wav文件進(jìn)行播放洋幻,就可以拿出前44字節(jié),然后讀出需要的參數(shù)使用AudioTrack進(jìn)行播放歇拆。