<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6560字,需瀏覽 14分鐘

           ·

          2021-05-19 13:16

          ??

          becfe58f2fda48ba4261b9a5cf182d8e.webp
          PS:本周關(guān)鍵詞『思考』、『行動』、『堅持』、『自律』。
          本文介紹下 Android音視頻開發(fā)中的AudioRecord的使用,案例將會在前面MediaCodec錄制MP4的基礎(chǔ)上進(jìn)行,使用AudioRecord錄制音頻數(shù)據(jù)并將其合成到MP4中,Android音視頻同系列文章如下:本文的主要內(nèi)容如下:
          1. AudioRecord介紹
          2. AudioRecord生命周期
          3. AudioRecord音頻數(shù)據(jù)讀取
          4. 直接緩沖區(qū)和字節(jié)序
          5. 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_RECORDINGRECORDSTATE_STOPPED,分別對應(yīng)未初始化、已初始化、錄制中、停止錄制,如下圖所示:

          c5a21e3d3484ccba4d642a780b584ee3.webp

          簡單說明一下:
          1. 未創(chuàng)建之前或者release之后AudioRecord都進(jìn)入STATE_UNINITIALIZED狀態(tài)。
          2. 創(chuàng)建AudioRecord時進(jìn)入STATE_INITIALIZED狀態(tài)。
          3. 調(diào)用startRecording進(jìn)入RECORDSTATE_RECORDING狀態(tài)。
          4. 調(diào)用stop進(jìn)入RECORDSTATE_STOPPED狀態(tài)。
          那么如何獲取AudioRecord的狀態(tài)呢,可以通過getStategetRecordingState獲取其狀態(tài),為保證正確使用可在使用AudioRecord對象操作之前進(jìn)行其狀態(tài)的判斷。

          AudioRecord音頻數(shù)據(jù)讀取

          AudioRecord 提供的三種讀取音頻數(shù)據(jù)的方式,如下:
          //?1.?讀取音頻數(shù)據(jù),音頻格式為AudioFormat#ENCODING_PCM_8BIT
          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)
          讀取音頻數(shù)據(jù)的返回值大于等于 0,讀取音頻數(shù)據(jù)常見異常如下:
          1. ERROR_INVALID_OPERATION:表示AudioRecord 未初始化。
          2. ERROR_BAD_VALUE:表示參數(shù)無效。
          3. 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ù),此時,該Bufferposition將保持不變,緩沖區(qū)中的數(shù)據(jù)的音頻格式則取決于AudioRecord中指定的格式,且字節(jié)存放的方式為本機(jī)字節(jié)序。

          直接緩沖區(qū)和字節(jié)序

          上面提到了兩個概念直接緩沖區(qū)和字節(jié)序,這里簡單說明一下:
          直接緩沖區(qū)
          DirectBuffer 是 NIO 里面的東西,這里簡單看下普通緩沖區(qū)和直接緩沖區(qū)的一些區(qū)別。
          • 普通緩沖區(qū)
          ByteBuffer?buf?=?ByteBuffer.allocate(1024);
          public?static?ByteBuffer?allocate(int?capacity)?{
          ????if?(capacity?<?0)
          ????????throw?new?IllegalArgumentException();
          ????return?new?HeapByteBuffer(capacity,?capacity);
          }
          可知普通緩沖區(qū)從堆上分配一個字節(jié)緩沖區(qū),該緩沖區(qū)受 JVM 的管理,意味著在合適的時候是可以被 GC 回收的,GC 回收伴隨著內(nèi)存的整理,某種程度上對性能是有影響的。
          • 直接緩沖區(qū)
          ByteBuffer?buf?=?ByteBuffer.allocateDirect(1024);
          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);
          }
          上面是 Android 中的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)鍵步驟如下:
          1. 開啟一個線程使用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ù)寫入文件即可。
          1. 讀取到音頻數(shù)據(jù)要想合成到MP4中需要先進(jìn)行音頻數(shù)據(jù)的編碼,音頻數(shù)據(jù)編碼器配置如下:
          //?音頻數(shù)據(jù)編碼器配置
          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")
          }
          關(guān)于編碼也就是MediaCodec的使用可以參考前面下面兩篇文章:這里使用MediaCodec的異步處理模式進(jìn)行音頻數(shù)據(jù)的編碼,這里將不貼代碼了,注意一點(diǎn)就是填充和釋放Buffer的時候一定要判斷條件,如果InputBuffer一直不釋放則會導(dǎo)致無可用的InputBuffer使用導(dǎo)致音頻編碼失敗,還有就是流結(jié)束的處理。
          1. 文件的合成使用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)鍵字獲取。推薦閱讀:
          瀏覽 67
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天干天天日天天色 | 91操操干 | 久久艹免费视频 | 色情电影A片 | 超碰人人国产 |