AudioRecord采集音頻數(shù)據(jù)及合成
??

PS:本周關(guān)鍵詞『思考』、『行動』、『堅持』、『自律』。本文介紹下
Android音視頻開發(fā)中的AudioRecord的使用,案例將會在前面MediaCodec錄制MP4的基礎(chǔ)上進(jìn)行,使用AudioRecord錄制音頻數(shù)據(jù)并將其合成到MP4中,Android音視頻同系列文章如下:本文的主要內(nèi)容如下:- AudioRecord介紹
- AudioRecord生命周期
- AudioRecord音頻數(shù)據(jù)讀取
- 直接緩沖區(qū)和字節(jié)序
- AudioRecord使用
AudioRecord介紹
AudioRecord 是 Android 中用來錄制硬件設(shè)備的音頻工具,通過 pulling的方式獲取音頻數(shù)據(jù),一般用來獲得原始音頻 PCM格式的數(shù)據(jù),可以實(shí)現(xiàn)邊錄邊播,多用于音頻數(shù)據(jù)的實(shí)時處理。創(chuàng)建AudioRecord的參數(shù)及說明如下://?創(chuàng)建AudioRecord
public?AudioRecord?(int?audioSource,?
????????????????int?sampleRateInHz,?
????????????????int?channelConfig,?
????????????????int?audioFormat,?
????????????????int?bufferSizeInBytes)
- audioSource:表示音頻源,音頻源定義在
MediaRecorder.AudioSource中,如常見的音頻源主麥克風(fēng)MediaRecorder.AudioSource.MIC等。 - sampleRateInHz:表示以赫茲為單位的采樣率,其含義是每個通道每秒的采樣數(shù),常見采樣率中只有 44100Hz 的采樣率可以保證在所有設(shè)備上正常使用,可以通過
getSampleRate獲取實(shí)際采樣率,這個采樣率不是音頻內(nèi)容播放的采樣率,比如可以在采樣率為 48000Hz 的設(shè)備上播放采樣率為 8000Hz 的聲音,對應(yīng)平臺會自動處理采樣率轉(zhuǎn)換,因此不會以 6 倍的速度播放。 - channelConfig:表示聲道數(shù),聲道定義在
AudioFormat中,常見的聲道中只有單聲道AudioFormat.CHANNEL_IN_MONO能保證在所有設(shè)備上正常使用,其他的比如AudioFormat.CHANNEL_IN_STEREO表示雙聲道,也就是立體聲。 - audioFormat:表示
AudioRecord返回的音頻數(shù)據(jù)的格式,對于線性PCM來說,反應(yīng)每個樣本大?。?、16、32位)及表現(xiàn)形式(整型、浮點(diǎn)型),音頻格式定義在AudioFormat中,常見的音頻數(shù)據(jù)格式中只有AudioFormat.ENCODING_PCM_16BIT可以保證在所有的設(shè)備上正常使用,像AudioFormat.ENCODING_PCM_8BIT不能保證在所有設(shè)備上正常使用。 - bufferSizeInBytes:表示寫入音頻數(shù)據(jù)的緩沖區(qū)的大小,該值不能小于
getMinBufferSize的大小,即不能小于AudioRecord所需的最小緩沖區(qū)的大小,否則將導(dǎo)致AudioRecord初始化失敗,該緩沖區(qū)大小并不能保證在負(fù)載情況下順利錄制,必要時可選擇更大值。
AudioRecord生命周期
AudioRecord的生命周期狀態(tài)包括 STATE_UNINITIALIZED、STATE_INITIALIZED、RECORDSTATE_RECORDING和RECORDSTATE_STOPPED,分別對應(yīng)未初始化、已初始化、錄制中、停止錄制,如下圖所示:
- 未創(chuàng)建之前或者
release之后AudioRecord都進(jìn)入STATE_UNINITIALIZED狀態(tài)。 - 創(chuàng)建
AudioRecord時進(jìn)入STATE_INITIALIZED狀態(tài)。 - 調(diào)用
startRecording進(jìn)入RECORDSTATE_RECORDING狀態(tài)。 - 調(diào)用
stop進(jìn)入RECORDSTATE_STOPPED狀態(tài)。
AudioRecord的狀態(tài)呢,可以通過getState和getRecordingState獲取其狀態(tài),為保證正確使用可在使用AudioRecord對象操作之前進(jìn)行其狀態(tài)的判斷。AudioRecord音頻數(shù)據(jù)讀取
AudioRecord 提供的三種讀取音頻數(shù)據(jù)的方式,如下://?1.?讀取音頻數(shù)據(jù),音頻格式為AudioFormat#ENCODING_PCM_8BIT讀取音頻數(shù)據(jù)的返回值大于等于 0,讀取音頻數(shù)據(jù)常見異常如下:
int?read(@NonNull?byte[]?audioData,?int?offsetInBytes,?int?sizeInBytes)
//?2.?讀取音頻數(shù)據(jù),音頻格式為AudioFormat#ENCODING_PCM_16BIT
int?read(@NonNull?short[]?audioData,?int?offsetInShorts,?int?sizeInShorts)
//?3.?讀取音頻數(shù)據(jù),見后面章節(jié)
int?read(@NonNull?ByteBuffer?audioBuffer,?int?sizeInBytes)
- ERROR_INVALID_OPERATION:表示
AudioRecord未初始化。 - ERROR_BAD_VALUE:表示參數(shù)無效。
- ERROR_DEAD_OBJECT:表示已經(jīng)傳輸了一些音頻數(shù)據(jù)的情況下不返回錯誤碼,將在下次
read返回處返回錯誤碼。
read 函數(shù)都是從硬件音頻設(shè)備讀取音頻數(shù)據(jù),前兩個主要的區(qū)別就是音頻格式不同,分別是 8 位、16 ?位,對應(yīng)的量化等級則是 2^8 和 2^16 量化等級。第三個read函數(shù)在讀取音頻數(shù)據(jù)時,會將其記錄在直接緩沖區(qū)(DirectBuffer)中,如果此緩沖區(qū)不是 DirectBuffer 則一直返回 0,也就是使用第三個read函數(shù)時傳入的參數(shù)audioBuffer必須是一個 DirectBuffer,否則不能正確讀取到音頻數(shù)據(jù),此時,該Buffer的position將保持不變,緩沖區(qū)中的數(shù)據(jù)的音頻格式則取決于AudioRecord中指定的格式,且字節(jié)存放的方式為本機(jī)字節(jié)序。直接緩沖區(qū)和字節(jié)序
上面提到了兩個概念直接緩沖區(qū)和字節(jié)序,這里簡單說明一下:直接緩沖區(qū)
DirectBuffer 是 NIO 里面的東西,這里簡單看下普通緩沖區(qū)和直接緩沖區(qū)的一些區(qū)別。- 普通緩沖區(qū)
ByteBuffer?buf?=?ByteBuffer.allocate(1024);可知普通緩沖區(qū)從堆上分配一個字節(jié)緩沖區(qū),該緩沖區(qū)受 JVM 的管理,意味著在合適的時候是可以被 GC 回收的,GC 回收伴隨著內(nèi)存的整理,某種程度上對性能是有影響的。
public?static?ByteBuffer?allocate(int?capacity)?{
????if?(capacity?<?0)
????????throw?new?IllegalArgumentException();
????return?new?HeapByteBuffer(capacity,?capacity);
}
- 直接緩沖區(qū)
ByteBuffer?buf?=?ByteBuffer.allocateDirect(1024);上面是 Android 中的
public?static?ByteBuffer?allocateDirect(int?capacity)?{
????//?Android-changed:?Android's?DirectByteBuffers?carry?a?MemoryRef.
????//?return?new?DirectByteBuffer(capacity);
????DirectByteBuffer.MemoryRef?memoryRef?=?new?DirectByteBuffer.MemoryRef(capacity);
????return?new?DirectByteBuffer(capacity,?memoryRef);
}
DirectBuffer 的實(shí)現(xiàn),可見是從內(nèi)存中分配的,這種方式獲得的緩沖區(qū)的獲取成本是釋放成本都是巨大的,但是可以駐留在垃圾回收堆的外部,一般分配給大型、壽命長的緩沖區(qū),最后分配此緩沖區(qū)能夠帶來顯著的性能提升才進(jìn)行分配,是否是DirectBuffer 可以通過 isDirect來確定。字節(jié)序
字節(jié)序指的是字節(jié)在內(nèi)存中的存放方式,字節(jié)序主要分為兩類:BIG-ENDIAN和LITTLE-ENDIAN,通俗的稱之為網(wǎng)絡(luò)字節(jié)序和本機(jī)字節(jié)序,具體如下:- 本機(jī)字節(jié)序,即 LITTLE-ENDIAN(小字節(jié)序、低字節(jié)序),即低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端,與之對應(yīng)的還有網(wǎng)絡(luò)字節(jié)序。
- 網(wǎng)絡(luò)字節(jié)序,一般指的是 TCP/IP 協(xié)議中使用的字節(jié)序,因?yàn)??TCP/IP ?各層協(xié)議將字節(jié)序定義為 BIG-ENDIAN,所以網(wǎng)絡(luò)字節(jié)序一般指的是 BIG-ENDIAN。
AudioRecord的使用
記得在前面的文章 Camera2、MediaCodec錄制mp4 中只是錄制了視頻,側(cè)重于MediaCodec的使用,這里將在視頻錄制的基礎(chǔ)上使用AudioRecord添加音頻的錄制,并將其合成到MP4文件中,其關(guān)鍵步驟如下:- 開啟一個線程使用
AudioRecord讀取硬件的音頻數(shù)據(jù),開線程可以避免卡頓,文末案例中也有代碼示例,見AudioEncode2,參考如下:
/**這里提一下,如果只是使用
?*?音頻讀取Runnable
?*/
class?RecordRunnable?:?Runnable{
????override?fun?run()?{
????????val?byteArray?=?ByteArray(bufferSize)
????????//?錄制狀態(tài)?-1表示默認(rèn)狀態(tài),1表述錄制狀態(tài),0表示停止錄制
????????while?(recording?==?1){
????????????val?result?=?mAudioRecord.read(byteArray,?0,?bufferSize)
????????????if?(result?>?0){
????????????????val?resultArray?=?ByteArray(result)
????????????????System.arraycopy(byteArray,?0,?resultArray,?0,?result)
????????????????quene.offer(resultArray)
????????????}
????????}
????????//?自定義流結(jié)束的數(shù)據(jù)
????????if?(recording?==?0){
????????????val?stopArray?=?byteArrayOf((-100).toByte())
????????????quene.offer(stopArray)
????????}
????}
}
AudioRecord錄制音頻數(shù)據(jù),當(dāng)讀取到音頻數(shù)據(jù)可將音頻數(shù)據(jù)寫入文件即可。- 讀取到音頻數(shù)據(jù)要想合成到
MP4中需要先進(jìn)行音頻數(shù)據(jù)的編碼,音頻數(shù)據(jù)編碼器配置如下:
//?音頻數(shù)據(jù)編碼器配置關(guān)于編碼也就是
private?fun?initAudioCodec()?{
????L.i(TAG,?"init?Codec?start")
????try?{
????????val?mediaFormat?=
????????????MediaFormat.createAudioFormat(
????????????????MediaFormat.MIMETYPE_AUDIO_AAC,
????????????????RecordConfig.SAMPLE_RATE,
????????????????2
????????????)
????????mAudioCodec?=?MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
????????mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE,?96000)
????????mediaFormat.setInteger(
????????????MediaFormat.KEY_AAC_PROFILE,
????????????MediaCodecInfo.CodecProfileLevel.AACObjectLC
????????)
????????mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE,?8192)
????????mAudioCodec.setCallback(this)
????????mAudioCodec.configure(mediaFormat,?null,?null,?MediaCodec.CONFIGURE_FLAG_ENCODE)
????}?catch?(e:?Exception)?{
????????L.i(TAG,?"init?error:${e.message}")
????}
????L.i(TAG,?"init?Codec?end")
}
MediaCodec的使用可以參考前面下面兩篇文章:這里使用MediaCodec的異步處理模式進(jìn)行音頻數(shù)據(jù)的編碼,這里將不貼代碼了,注意一點(diǎn)就是填充和釋放Buffer的時候一定要判斷條件,如果InputBuffer一直不釋放則會導(dǎo)致無可用的InputBuffer使用導(dǎo)致音頻編碼失敗,還有就是流結(jié)束的處理。- 文件的合成使用
MediaMuxer,MediaMuxer在啟動之前必須確保添加好視軌和音軌
override?fun?onOutputFormatChanged(codec:?MediaCodec,?format:?MediaFormat)?{
????L.i(TAG,?"onOutputFormatChanged?format:${format}")
????//?添加音軌
????addAudioTrack(format)
????//?如果音軌和視軌都添加的情況下才啟動MediaMuxer
????if?(RecordConfig.videoTrackIndex?!=?-1)?{
????????mAudioMuxer.start()
????????RecordConfig.isMuxerStart?=?true
????????L.i(TAG,?"onOutputFormatChanged?isMuxerStart:${RecordConfig.isMuxerStart}")
????}
}
//?添加音軌
private?fun?addAudioTrack(format:?MediaFormat)?{
????L.i(TAG,?"addAudioTrack?format:${format}")
????RecordConfig.audioTrackIndex?=?mAudioMuxer.addTrack(format)
????RecordConfig.isAddAudioTrack?=?true
}
//?...
AudioRecord的使用基本如上,AudioRecord錄制音頻并合成到MP4的案例代碼可以回復(fù)關(guān)鍵字【record】關(guān)鍵字獲取。推薦閱讀:評論
圖片
表情
