Android音视频系列(七):PCM音频单声道与双声道的相互转换
前言
上一篇我們已經(jīng)學(xué)習(xí)了PCM音頻的保存格式,這一篇我們通過掌握的知識,完成PCM音頻的單聲道和雙聲道的互相轉(zhuǎn)換。
正文
首先我們把上一篇的最核心部分貼出來:
 
 我們首先完成單聲道轉(zhuǎn)雙聲道的操作。
單聲道轉(zhuǎn)雙聲道
單聲道轉(zhuǎn)雙聲道的基本原理:
由圖可知,我們需要把單聲道的每一份數(shù)據(jù)都拷貝一份到右聲道,這樣使用雙聲道播放就沒有問題了。
首先我錄制了一個音頻保存到ArrayList中:
private val recordThread = Thread(Runnable {val iMinBufferSize = AudioRecord.getMinBufferSize(Constants.SAMPLE_RATE,currentChannel,AudioFormat.ENCODING_PCM_16BIT)val audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC,Constants.SAMPLE_RATE,currentChannel,AudioFormat.ENCODING_PCM_16BIT,iMinBufferSize)audioRecord.startRecording()monoByteList.clear()val recordBytes = ByteArray(iMinBufferSize)var lastTime = 0Lvar pcmSize = 0while (lastTime < recordTime * 1000000L) {val readSize = audioRecord.read(recordBytes, 0, recordBytes.size)// 保存音頻數(shù)據(jù)到ArrayList中monoByteList.addAll(recordBytes.asList())pcmSize += readSizelastTime = pcmSize * 1000000L / 2 / Constants.SAMPLE_RATE}audioRecord.stop()audioRecord.release()recordCallback()} )錄制的是16位的數(shù)據(jù),所以我們每一個采樣的數(shù)據(jù)會占據(jù)兩位,所以在拷貝的過程中,我們也要每兩位拷貝一次:
private val convertMonoToStereoThread = Thread(Runnable {// 單聲道轉(zhuǎn)雙聲道// 雙聲道的存儲格式為 LRLRLR// 所以把左聲道的內(nèi)容拷貝到右聲道即可for (index in 0 until monoByteList.size step 2) {// 目前保存的是16位的數(shù)據(jù),所以要復(fù)制前兩位stereoByteList.add(monoByteList[index])stereoByteList.add(monoByteList[index + 1])// 目前保存的是16位的數(shù)據(jù),所以要復(fù)制前兩位stereoByteList.add(monoByteList[index])stereoByteList.add(monoByteList[index + 1])}convertCallback() })單聲道轉(zhuǎn)聲道的操作就完成了。
雙聲道轉(zhuǎn)單聲道的操作
雙聲道轉(zhuǎn)單聲道的原理:
雙聲道轉(zhuǎn)單聲道有兩種做法:
 1、丟棄其中一路數(shù)據(jù)(丟失左聲道或右聲道的數(shù)據(jù))
 2、兩路數(shù)據(jù)相加的平局值。(也可以是其他算法)
第一種做法:丟棄一路數(shù)據(jù)
我們可以按照單聲道雙聲道的做法,每四位取前兩位或后兩位的數(shù)據(jù)即可。但是這里我們換一種做法。
// 保存了錄制的16位雙聲道音頻數(shù)據(jù),過程省略,里面保存類型Byte stereoByteList// 目標(biāo)輸出ArrayList,類型為Short,如果你需要Byte數(shù)據(jù),可以再自行轉(zhuǎn)換一次 monoByteList// 開始轉(zhuǎn)換 private fun convertStereoToMono() {thread {// 雙聲道轉(zhuǎn)單聲道// 方案1:丟掉一路數(shù)據(jù),此方法最簡單// 這里只取左聲道的聲音monoByteList.clear()// ByteOrder.LITTLE_ENDIAN 從小到大 ,高位在后// ByteOrder.BIG_ENDIAN 從大到小,高位在前,默認(rèn)val shortBuffer = ByteBuffer.wrap(stereoByteList.toByteArray()).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()for (index in 0 until shortBuffer.capacity() step 2) {monoByteList.add(shortBuffer.get(index))}convertCallback()}}這里我們使用了ByteBuffer幫助我們把Byte轉(zhuǎn)成Short。其中有一個很重要的坑,就是設(shè)置Byte轉(zhuǎn)Short的規(guī)則:
ByteOrder.LITTLE_ENDIAN 從小到大 ,高位在后
 ByteOrder.BIG_ENDIAN 從大到小,高位在前,默認(rèn)
short的長度為16位,所以需要兩個8位的Byte一起保存,其中一個Byte保存的是前8位,也就是高位另外的一個Byte保存的后8位,也就是低位。
 所以我們一定要確保高低位的順序,否則得到的Short一定是錯的,經(jīng)過測試,錄制的音頻是低位在前,所以我們修改ByteBuffer默認(rèn)的高位在前的配置:
相同的原理,我們需要Byte轉(zhuǎn)Int都可以借助對應(yīng)的Buffer進(jìn)行讀取,非常的方便。
第二種做法:左右聲道取平局值
// 保存了錄制的16位雙聲道音頻數(shù)據(jù),過程省略,里面保存類型Byte stereoByteList// 目標(biāo)輸出ArrayList,類型為Short,如果你需要Byte數(shù)據(jù),可以再自行轉(zhuǎn)換一次 monoByteListprivate fun convertStereoToMono() {thread {// 雙聲道轉(zhuǎn)單聲道monoByteList.clear()// ByteOrder.LITTLE_ENDIAN 從小到大 ,高位在后// ByteOrder.BIG_ENDIAN 從大到小,高位在前,默認(rèn)val shortBuffer = ByteBuffer.wrap(stereoByteList.toByteArray()).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()// 方案二:把左右聲道的聲音相加,取平均值// 使用kotlin的位運(yùn)算 and shl等,無法得到正確的byte轉(zhuǎn)short,short轉(zhuǎn)initfor (index in 0 until shortBuffer.capacity() step 2) {monoByteList.add((shortBuffer.get(index) + shortBuffer.get(index + 1) / 2).toShort())}convertCallback()}}基本流程和第一種方法一樣,如果是你用的Java,你還可以通過位運(yùn)算進(jìn)行Short和Byte的轉(zhuǎn)換,但是kotlin的對應(yīng)的運(yùn)算符卻無法正確轉(zhuǎn)換,具體原因還不清楚,這也是為什么我使用了Buffer進(jìn)行轉(zhuǎn)換的原因。
總結(jié)
只要我們掌握了PCM的保存格式,單聲道和雙聲道的互相轉(zhuǎn)換還是非常輕松的,下一篇我們來了解一下新的音頻格式:WAV。
總結(jié)
以上是生活随笔為你收集整理的Android音视频系列(七):PCM音频单声道与双声道的相互转换的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 分贝测试软件哪个好 家庭影院,家庭影院5
- 下一篇: 【教你在win7中开启四声道效果】
