Android音視頻系列(八):瞭解音頻格式WAV以及與PCM的轉換

前言

之前我們已經瞭解了PCM音頻數據,我們理解爲最原始的數據,雖然他的音質是最棒的,但是同時也暴露出兩個很重要的問題:

  1. 普通播放器無法播放,數據裏不包含任何跟音頻格式有關的信息(聲道,採樣率等等);
  2. 體積過大,傳輸效率低;

爲了解決上面的兩個問題,出現了更多的音頻格式。例如常見的:wav,mp3,aac等等。這一篇主要的內容就是了解wav。

正文

如果你想要錄製音頻並且輸入wav格式的文件,你會發現mediaCodec中並沒有這個格式。於是打開瀏覽器一頓操作,你會搜索到很多的資料,你會發現原來WAV和PCM原來這麼接近。

WAV主要解決了播放器無法播放的問題,體積上並沒有太大的優勢。WAV可以直接包含PCM,我們只需要在PCM的前面加入WAV的頭文件,就完成轉換了,所以我們首先要了解WAV的頭文件的內容。、

WAV頭文件

wav頭文件結構
上圖是一個完整的WAV頭文件的結構,其中一部分fact(壓縮編碼)在包含PCM是不需要的,因爲PCM的無損無壓縮的。

wav頭文件詳細結構圖
上圖是官方對於wav的頭文件描述圖,雖然是英文的,但是我們依次瞭解每一位表達的意義:

  1. ChunkID:固定RIFF的ACSⅡ碼,佔4位;
  2. ChunkSize:文件的總長度,佔4位,因爲不包含ChunkID和ChunkSize的長度,所以要需要減8;
  3. Format:固定WAVE的ASCⅡ碼,佔4位;
  4. Subchunk1 ID:fmt塊,佔4位,如果不足4位,補空格,所以是‘fmt ’;
  5. Subchunk Size:fmt塊的總長度,pcm固定16,表示從當前位置到描述fmt信息的長度,從上圖計算AudioFormat到BitsPerSample的長度,長度確實是16,如果不是PCM長度可能會發生變化,佔4位:
  6. AudioFormat:音頻格式,PCM固定是1,佔2位;
  7. NumChannels:聲道數,佔2位;
  8. SampleRate:採樣率,佔4位;
  9. ByteRate:比特率,佔4位;
  10. BlockAlign:計算方法爲 NumChannels * BitsPerSample/8,佔兩位;
  11. BitsPerSample:我們錄製的格式,一個採樣佔幾個byte,佔2位;
  12. Subchunk2ID:固定保存‘data’,佔4位;
  13. 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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章