前言
之前我們已經瞭解了PCM音頻數據,我們理解爲最原始的數據,雖然他的音質是最棒的,但是同時也暴露出兩個很重要的問題:
- 普通播放器無法播放,數據裏不包含任何跟音頻格式有關的信息(聲道,採樣率等等);
- 體積過大,傳輸效率低;
爲了解決上面的兩個問題,出現了更多的音頻格式。例如常見的:wav,mp3,aac等等。這一篇主要的內容就是了解wav。
正文
如果你想要錄製音頻並且輸入wav格式的文件,你會發現mediaCodec中並沒有這個格式。於是打開瀏覽器一頓操作,你會搜索到很多的資料,你會發現原來WAV和PCM原來這麼接近。
WAV主要解決了播放器無法播放的問題,體積上並沒有太大的優勢。WAV可以直接包含PCM,我們只需要在PCM的前面加入WAV的頭文件,就完成轉換了,所以我們首先要了解WAV的頭文件的內容。、
WAV頭文件
上圖是一個完整的WAV頭文件的結構,其中一部分fact(壓縮編碼)在包含PCM是不需要的,因爲PCM的無損無壓縮的。
上圖是官方對於wav的頭文件描述圖,雖然是英文的,但是我們依次瞭解每一位表達的意義:
- ChunkID:固定RIFF的ACSⅡ碼,佔4位;
- ChunkSize:文件的總長度,佔4位,因爲不包含ChunkID和ChunkSize的長度,所以要需要減8;
- Format:固定WAVE的ASCⅡ碼,佔4位;
- Subchunk1 ID:fmt塊,佔4位,如果不足4位,補空格,所以是‘fmt ’;
- Subchunk Size:fmt塊的總長度,pcm固定16,表示從當前位置到描述fmt信息的長度,從上圖計算AudioFormat到BitsPerSample的長度,長度確實是16,如果不是PCM長度可能會發生變化,佔4位:
- AudioFormat:音頻格式,PCM固定是1,佔2位;
- NumChannels:聲道數,佔2位;
- SampleRate:採樣率,佔4位;
- ByteRate:比特率,佔4位;
- BlockAlign:計算方法爲 NumChannels * BitsPerSample/8,佔兩位;
- BitsPerSample:我們錄製的格式,一個採樣佔幾個byte,佔2位;
- Subchunk2ID:固定保存‘data’,佔4位;
- Subchunk2Size:音頻數據的長度,如果你知道,計算方法爲: NumSamples * NumChannels * BitsPerSample/8;
經過計算,當WAV包含PCM數據時,頭文件的總長度爲44位。
PCM轉WAV
我們已經把WAV頭文件瞭解的輕輕楚楚,接下來就可以把PCM格式轉成WAV格式。
首次我們錄製一份PCM文件,並在文件的頭部提前預留了44byte的位置:
// 創建AudioRecord
AudioRecord(
MediaRecorder.AudioSource.MIC,
11025,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
getMinBufferSize()
)
// 創建wav文件,並預留wav頭文件的位置
val mWavFile = File(mFile.absolutePath)
mWriter = FileOutputStream(mWavFile).channel
val fakehead = ByteArray(44)
mWriter?.write(ByteBuffer.wrap(fakehead))
// 寫入錄製音頻
while (isRecording) {
val resultRead = audioRecord.read(byteArray, 0, byteArray.size)
for (i in 0 until result) {
mWriter.write(ByteBuffer.wrap(recordedBytes, 0, resultRead))
}
}
上面是一份僞代碼,我們錄製了一份音頻,並預留了wav頭文件的位置,接下來我們根據之前的理解,填入wav的信息:
fun getWaveFileHeader(
totalAudioLen: Long,
totalDataLen: Long,
longSampleRate: Long,
channels: Int,
byteRate: Long,
bitsPerSample: Int
)
{
val header = ByteArray(44)
// 1. ChunkID:固定RIFF的ACSⅡ碼,佔4位;
header[0] = 'R'.toByte() // RIFF/WAVE header
header[1] = 'I'.toByte()
header[2] = 'F'.toByte()
header[3] = 'F'.toByte()
//2. ChunkSize:文件的總長度,佔4位,因爲不包含ChunkID和ChunkSize的長度,所以要需要減8;
// 因爲int類型,所以我們需要對每一位byte對別保存int,跟之前的PCM的聲道轉換類似
header[4] = (totalDataLen and 0xff).toByte()
header[5] = (totalDataLen shr 8 and 0xff).toByte()
header[6] = (totalDataLen shr 16 and 0xff).toByte()
header[7] = (totalDataLen shr 24 and 0xff).toByte()
// 3. Format:固定WAVE的ASCⅡ碼,佔4位;
header[8] = 'W'.toByte() //WAVE
header[9] = 'A'.toByte()
header[10] = 'V'.toByte()
header[11] = 'E'.toByte()
// 4. Subchunk1 ID:fmt塊,佔4位,如果不足4位,補空格,所以是‘fmt ’;
header[12] = 'f'.toByte() // 'fmt ' chunk
header[13] = 'm'.toByte()
header[14] = 't'.toByte()
header[15] = ' '.toByte()
// 5. Subchunk Size:fmt塊的總長度,pcm固定16,表示從當前位置到描述fmt信息的長度
// 同理是int值,佔4位
header[16] = 16
header[17] = 0
header[18] = 0
header[19] = 0
// 6. AudioFormat:音頻格式,PCM固定是1,佔2位;
header[20] = 1 // format = 1
header[21] = 0
// 7. NumChannels:聲道數,佔2位;
header[22] = channels.toByte()
header[23] = 0
// 8. SampleRate:採樣率,佔4位;
header[24] = (longSampleRate and 0xff).toByte()
header[25] = (longSampleRate shr 8 and 0xff).toByte()
header[26] = (longSampleRate shr 16 and 0xff).toByte()
header[27] = (longSampleRate shr 24 and 0xff).toByte()
// 9. ByteRate:比特率,佔4位;
header[28] = (byteRate and 0xff).toByte()
header[29] = (byteRate shr 8 and 0xff).toByte()
header[30] = (byteRate shr 16 and 0xff).toByte()
header[31] = (byteRate shr 24 and 0xff).toByte()
//10. BlockAlign:計算方法爲 NumChannels * BitsPerSample/8,佔兩位;
header[32] = (channels * 16 / 8).toByte()
header[33] = 0
//11. BitsPerSample:我們錄製的格式,一個採樣佔幾個byte,佔2位;
header[34] = bitsPerSample// bits per sample
header[35] = 0
//12. Subchunk2ID:固定保存‘data’,佔4位;
header[36] = 'd'.toByte() //data
header[37] = 'a'.toByte()
header[38] = 't'.toByte()
header[39] = 'a'.toByte()
//14. Subchunk2Size:音頻數據的長度
header[40] = (totalAudioLen and 0xff).toByte()
header[41] = (totalAudioLen shr 8 and 0xff).toByte()
header[42] = (totalAudioLen shr 16 and 0xff).toByte()
header[43] = (totalAudioLen shr 24 and 0xff).toByte()
}
根據我們錄製的配置,我們可以對getWaveFileHeader方法傳入一下參數:
Util.getWaveFileHeader(
mWriter.size() - 44, // totalAudioLen, 音頻數據不包含wav頭文件,所以減44
mWriter.size() - 8, // totalDataLen總長度,記得減8
mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE).toLong(), // SampleRa
mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT), // channels
mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE).toLong(), // byteRate
16, // AudioFormat.ENCODING_PCM_16BIT = 16, AudioFormat.ENCODING_PCM_8BIT = 8
)
到此,我們錄製的PCM數據已經變成了播放器可播的WAV格式。
總結
這一篇我們理解了WAV和PCM的區別以及轉換方法,下一篇我們繼續學習新的音頻格式AAC。