日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android音频格式转换,Android音视频系列(八):了解音频格式WAV以及与PCM的转换...

發布時間:2023/12/20 Android 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android音频格式转换,Android音视频系列(八):了解音频格式WAV以及与PCM的转换... 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

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

普通播放器無法播放,數據里不包含任何跟音頻格式有關的信息(聲道,采樣率等等);

體積過大,傳輸效率低;

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

正文

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

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

WAV頭文件

wav頭文件結構

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

wav頭文件詳細結構圖

上圖是官方對于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。

總結

以上是生活随笔為你收集整理的Android音频格式转换,Android音视频系列(八):了解音频格式WAV以及与PCM的转换...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。