banner
jzman

jzman

Coding、思考、自觉。
github

Android音頻播放AudioTrack詳解

前面幾篇文章介紹了MediaCodecMediaMuxerAudioRecord等音視頻相關知識,這些都是 Android 音視頻開發中必須掌握的,相關文章鏈接如下:

Android 中常用的播放音頻的接口有MediaPlayerAudioTrackSoundPool,音頻的渲染最常用的是AudioTrackOpenSL ES,下面將介紹下AudioTrack相關知識,主要內容如下:

  1. AudioTrack 介紹
  2. AudioTrack 的創建
  3. AudioTrack 音頻數據寫入
  4. AudioTrack 生命週期
  5. AudioTrack 的使用

AudioTrack 介紹#

AudioTrack用來播放原始 pcm格式的音頻數據,AudioTrack有兩種播放模式:

  • MODE_STATIC:這種模式會將音頻數據一次寫入音頻緩衝區,適合處理內存少及盡可能小的延遲播放的短聲音場景,如播放的遊戲音效、鈴聲、系統提示音等,此時這種模式開銷最小。
  • MODE_STREAM:這種模式會不斷的寫入音頻數據,適用於需要不斷接受音頻數據的場景,這種模式主要是由於某些音頻數據持續時間長、或者音頻特性(高採樣率、更高位深等)導致不能一次性寫入內存而出現的,正常播放 PCM原始音頻數據就選擇這種模式。

MediaPlayer相較,MediaPlayer可以播放不同類型、不同格式的聲音文件,會在底層創建與之對應的音頻解碼器,而AudioTrack只接收PCM原始音頻數據,MediaPlayer在底層還是會創建AudioTrack,把解碼後的PCM數流傳遞給AudioTrackAudioTrack再傳遞給AudioFlinger進行混音,然後才傳遞給硬件播放。

AudioTrack 的創建#

AudioTrack的創建使用如下方式:

// Android5.0開始
AudioTrack(
    attributes: AudioAttributes!, 
    format: AudioFormat!, 
    bufferSizeInBytes: Int, 
    mode: Int, 
    sessionId: Int)

上面構造方法對應的參數含義如下:

  • attributes:表示音頻流信息的屬性集合,自從 Android5.0 開始使用AudioAttributes來取代流類型的設置,可以比流類型設置傳達更多信息,常用來設置音頻的用途、音頻的內容等。
  • format:表示AudioTrack 接受的音頻格式,對於線性 PCM來說,反應每個樣本大小(8、16、32 位)及表現形式(整型、浮點型),音頻格式定義在AudioFormat中,常見的音頻數據格式中只有AudioFormat.ENCODING_PCM_16BIT可以保證在所有的設備上正常使用,像AudioFormat.ENCODING_PCM_8BIT不能保證在所有設備上正常使用。
  • bufferSizeInBytes:表示音頻數據緩衝區的大小,單位是字節,其大小一般是音頻幀大小的非零倍數,如果播放模式是MODE_STATIC,則緩衝區大小是本次播放的音頻的大小,如果播放模式是MODE_STREAM,則緩衝區大小不能小於最小緩衝區大小,也就是不能小於getMinBufferSize返回的大小。
  • mode:表示播放模式,AudioTrack提供了MODE_STATICMODE_STREAM兩種方式,MODE_STATIC會將音頻資源一次性寫入音頻緩衝區,適用於鈴聲、系統提示音等延遲小、音頻資源內存佔用少的場景,MODE_STREAM則適用於需要不斷通過write方法寫入數據的場景,相較MODE_STATIC會有一定延遲,但是可以持續不斷的接收音頻數據。
  • sessionId:音頻會話 Id,這裡使用AudioManager.AUDIO_SESSION_ID_GENERATE由底層音頻框架自己生成sessionId

AudioTrack 音頻數據寫入#

無論是流模式 (STREAM_MODE) 還是靜態緩衝模式 (STATIC_MODE) 模式,都需通過write方式寫入音頻數據來進行播放,主要的write方式如下:

// AudioTrack構造函數中指定的格式應為AudioFormat#ENCODING_PCM_8BIT
open fun write(audioData: ByteArray, offsetInBytes: Int, sizeInBytes: Int): Int
// AudioTrack構造函數中指定的格式應為AudioFormat#ENCODING_PCM_16BIT 
open fun write(audioData: ShortArray, offsetInShorts: Int, sizeInShorts: Int): Int
// AudioTrack構造函數中指定的格式應為AudioFormat#ENCODING_PCM_FLOAT
open fun write(audioData: FloatArray, offsetInFloats: Int, sizeInFloats: Int, writeMode: Int): Int

寫入音頻數據的返回值大於等於 0,讀取音頻數據常見異常如下:

  1. ERROR_INVALID_OPERATION:表示AudioTrack 未初始化。
  2. ERROR_BAD_VALUE:表示參數無效。
  3. ERROR_DEAD_OBJECT:表示已經傳輸了一些音頻數據的情況下不返回錯誤碼,將在下次 write返回處返回錯誤碼。

這個跟AudioRecord中的 read 函數有點類似,具體細節查看官方文檔。

AudioTrack 生命週期#

AudioTrack的生命週期主要是STATE_UNINITIALIZEDSTATE_INITIALIZEDSTATE_NO_STATIC_DATA,其中STATE_INITIALIZED對應STREAM_MODESTATE_NO_STATIC_DATA對應STATIC_MODE,至於播放狀態不怎麼重要,如下圖所示:

Mermaid Loading...

AudioTrack 的使用#

AudioTrack的使用主要就是從PCM文件中讀取數據,然後將讀取到的音頻寫入AudioTrack進行播放,其關鍵代碼如下:

// 初始化AudioTrack
private fun initAudioTrack() {
    bufferSize = AudioTrack
        .getMinBufferSize(SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT)
    attributes = AudioAttributes.Builder()
        .setUsage(AudioAttributes.USAGE_MEDIA) // 設置音頻的用途
        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) // 設置音頻的內容類型
        .build()
    audioFormat = AudioFormat.Builder()
        .setSampleRate(SAMPLE_RATE)
        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
        .build()
    audioTrack = AudioTrack(
        attributes, audioFormat, bufferSize,
        AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE
    )
}
// AudioTrack寫入音頻數據
private fun writeAudioData(){
    scope.launch(Dispatchers.IO){
        val pcmFile = File(pcmFilePath)
        val ins = FileInputStream(pcmFile)
        val bytes = ByteArray(bufferSize)
        var len: Int
        while (ins.read(bytes).also { len = it } > 0){
            audioTrack.write(bytes, 0, len)
        }
        audioTrack.stop()
    }
}
// 開始播放
private fun start(){
    audioTrack.play()
    writeAudioData()
}

AudioTrack的使用基本如上,AudioTrack播放音頻的相關代碼如感興趣可留言獲取。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。