一娩井、wav 和 pcm
一般通過麥克風采集的錄音數(shù)據(jù)都是PCM格式的,即不包含頭部信息锦茁,播放器無法知道音頻采樣率攘轩、位寬等參數(shù),導致無法播放码俩,顯然是非常不方便的度帮。pcm轉換成wav,我們只需要在pcm的文件起始位置加上至少44個字節(jié)的WAV頭信息即可稿存。
RIFF
- WAVE文件是以RIFF(Resource Interchange File Format, "資源交互文件格式")格式來組織內(nèi)部結構的
RIFF文件結構可以看作是樹狀結構,其基本構成是稱為"塊"(Chunk)的單元.
- WAVE文件是由若干個Chunk組成的笨篷。按照在文件中的出現(xiàn)位置包括:RIFF WAVE Chunk, Format Chunk, Fact Chunk(可選), Data Chunk。
Fact Chunk 在壓縮后或在非PCM編碼時存在
二瓣履、WAV頭文件
所有的WAV都有一個文件頭率翅,這個文件頭記錄著音頻流的編碼參數(shù)。數(shù)據(jù)塊的記錄方式是little-endian字節(jié)順序拂苹。
image.png
偏移地址 | 命名 | 內(nèi)容 |
---|---|---|
00-03 | ChunkId | "RIFF" |
04-07 | ChunkSize | 下個地址開始到文件尾的總字節(jié)數(shù)(此Chunk的數(shù)據(jù)大小) |
08-11 | fccType | "WAVE" |
12-15 | SubChunkId1 | "fmt ",最后一位空格安聘。 |
16-19 | SubChunkSize1 | 一般為16,表示fmt Chunk的數(shù)據(jù)塊大小為16字節(jié)瓢棒,即20-35 |
20-21 | FormatTag | 1:表示是PCM 編碼 |
22-23 | Channels | 聲道數(shù)浴韭,單聲道為1,雙聲道為2 |
24-27 | SamplesPerSec | 采樣率 |
28-31 | BytesPerSec | 碼率 :采樣率 * 采樣位數(shù) * 聲道個數(shù)脯宿,bytePerSecond = sampleRate * (bitsPerSample / 8) * channels |
32-33 | BlockAlign | n 每次采樣的大心罹薄:位寬*聲道數(shù)/8 |
34-35 | BitsPerSample | 位寬 |
36-39 | SubChunkId2 | "data" |
40-43 | SubChunkSize2 | 音頻數(shù)據(jù)的長度 |
44-... | data | 音頻數(shù)據(jù) |
三、java 生成頭文件
public static class WavHeader {
/**
* RIFF數(shù)據(jù)塊
*/
final String riffChunkId = "RIFF";
int riffChunkSize;
final String riffType = "WAVE";
/**
* FORMAT 數(shù)據(jù)塊
*/
final String formatChunkId = "fmt ";
final int formatChunkSize = 16;
final short audioFormat = 1;
short channels;
int sampleRate;
int byteRate;
short blockAlign;
short sampleBits;
/**
* FORMAT 數(shù)據(jù)塊
*/
final String dataChunkId = "data";
int dataChunkSize;
WavHeader(int totalAudioLen, int sampleRate, short channels, short sampleBits) {
this.riffChunkSize = totalAudioLen;
this.channels = channels;
this.sampleRate = sampleRate;
this.byteRate = sampleRate * sampleBits / 8 * channels;
this.blockAlign = (short) (channels * sampleBits / 8);
this.sampleBits = sampleBits;
this.dataChunkSize = totalAudioLen - 44;
}
public byte[] getHeader() {
byte[] result;
result = ByteUtils.merger(ByteUtils.toBytes(riffChunkId), ByteUtils.toBytes(riffChunkSize));
result = ByteUtils.merger(result, ByteUtils.toBytes(riffType));
result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkId));
result = ByteUtils.merger(result, ByteUtils.toBytes(formatChunkSize));
result = ByteUtils.merger(result, ByteUtils.toBytes(audioFormat));
result = ByteUtils.merger(result, ByteUtils.toBytes(channels));
result = ByteUtils.merger(result, ByteUtils.toBytes(sampleRate));
result = ByteUtils.merger(result, ByteUtils.toBytes(byteRate));
result = ByteUtils.merger(result, ByteUtils.toBytes(blockAlign));
result = ByteUtils.merger(result, ByteUtils.toBytes(sampleBits));
result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkId));
result = ByteUtils.merger(result, ByteUtils.toBytes(dataChunkSize));
return result;
}
}
四连霉、PCM轉Wav
public class WavUtils {
private static final String TAG = WavUtils.class.getSimpleName();
/**
* 生成wav格式的Header
* wave是RIFF文件結構榴芳,每一部分為一個chunk嗡靡,其中有RIFF WAVE chunk,
* FMT Chunk窟感,F(xiàn)act chunk(可選),Data chunk
*
* @param totalAudioLen 不包括header的音頻數(shù)據(jù)總長度
* @param sampleRate 采樣率,也就是錄制時使用的頻率
* @param channels audioRecord的頻道數(shù)量
* @param sampleBits 位寬
*/
public static byte[] generateWavFileHeader(int totalAudioLen, int sampleRate, int channels, int sampleBits) {
WavHeader wavHeader = new WavHeader(totalAudioLen, sampleRate, (short) channels, (short) sampleBits);
return wavHeader.getHeader();
}
}
/**
* 將header寫入到pcm文件中 不修改文件名
*
* @param file 寫入的pcm文件
* @param header wav頭數(shù)據(jù)
*/
public static void writeHeader(File file, byte[] header) {
if (!FileUtils.isFile(file)) {
return;
}
RandomAccessFile wavRaf = null;
try {
wavRaf = new RandomAccessFile(file, "rw");
wavRaf.seek(0);
wavRaf.write(header);
wavRaf.close();
} catch (Exception e) {
Logger.e(e, TAG, e.getMessage());
} finally {
try {
if (wavRaf != null) {
wavRaf.close();
}
} catch (IOException e) {
Logger.e(e, TAG, e.getMessage());
}
}
private void makeFile() {
mergePcmFiles(recordFile, files);
//這里實現(xiàn)上一篇未完成的工作
byte[] header = WavUtils.generateWavFileHeader((int) resultFile.length(), currentConfig.getSampleRate(), currentConfig.getChannelCount(), currentConfig.getEncoding());
WavUtils.writeHeader(resultFile, header);
Logger.i(TAG, "錄音完成讨彼! path: %s ; 大惺疗怼:%s", recordFile.getAbsoluteFile(), recordFile.length());
}