天天看點

Android音頻基礎與錄制播放

作者:音視訊流媒體技術

一、音頻概覽

聲音是振動産生的能量(聲波),通過媒體傳播并能被人或動物聽覺器官所感覺的波動現象。例如人類說話時通過聲帶振動産生聲波,通過空氣傳播後到達其他人耳朵。

要采集聲音,就需要通過麥克風對聲音進行錄制。錄音就是将聲音轉為模拟信号或機械記錄的過程。而數字錄音是進一步将模拟信号經由ADC(模拟數字轉換器)将類比取樣成數字記錄到儲存設備。

在Android開發音頻,就是利用AudioRecoder、MediaRecoder錄音,通過AudioTrack、MediaPlayer播放聲音,而這一過程會涉及到很多知識點,例如錄制聲音過程的采樣率、位深、編解碼MediaCodec、FFmpeg等等。

總結來說:

  1. 音頻錄制與播放(Android相關類的使用,Framework調用原理)
  2. 音頻處理(去噪、靜音檢測、回聲消除、音效處理、功放/增強、混音分離)
  3. 音頻的編解碼和格式轉換(MediaCodec、FFmpeg)
  4. 音頻傳輸協定(SIP\A2DP、AVRCP等)

1、PCM

PCM全名叫脈沖編碼調制,是一種将模拟信号數字化的方法。PCM将信号的強度依照同樣的間距分成數段,然後使用獨特的數字記号(二進制)量化。這一過程通常由ADC來實作。作為Android程式員,更多是調用AudioTrack錄制音頻後,儲存為PCM編碼格式,也可以再進一步編碼,存儲成其它格式。

例如下圖(正常應該是正弦波):

用綠色圓圈來表示連續的模拟信号,而紅色圓點則表示數字信号。PCM過程就是在模拟信号上進行采樣和收集,形成斷斷續續的采樣點,也就是數字信号。在X軸方向上,可以了解為對音頻的量化是時間,展現在采樣率上。在Y軸方向,可以了解為量化是數量,展現為位深。

Android音頻基礎與錄制播放

2、采樣率

采樣率表示在1秒内對聲音的模拟信号采樣的次數,假設上面是聲音在1秒内的模拟信号,那麼采樣就隻有2次,以頻率作為計量機關,是以這裡采樣率2Hz。由此 ,可以看出采樣率越高,采樣得到的數字信号資料就越多,聲音在播放過程就會越真實越接近原聲,但這也占用很大的資料存儲。是以需要根據人耳能識别頻率的範圍和具體的使用場景,将采樣率控制在合适的範圍内。常用的音頻采樣率為44.1k Hz和48k Hz

人耳能識别的頻率在20~20kHz,根據奈奎斯特·香農采樣定理,采樣後的音頻要還原成人耳可以識别聲音,采樣率需要為人耳可以識别頻率的2倍,而人耳上限是20KHz,2倍就是40kHz。是以常用的音頻采樣率為44.1k Hz和48k Hz。其他常用采樣頻率:

  • 8k Hz - 電話所用采樣率, 對于人的說話已經足夠

    11.025k Hz - AM調幅廣播所用采樣率

    22.050k Hz和24.000k Hz - FM調頻廣播所用采樣率

    32k Hz - miniDV 數位視訊 camcorder、DAT (LP mode)所用采樣率

    44.1k Hz - 音頻 CD, 也常用于 MPEG-1 音頻(VCD, SVCD, MP3)所用采樣率

    47.25k Hz - 商用 PCM 錄音機所用采樣率

    48k Hz - miniDV、數字電視、DVD、DAT、電影和專業音頻所用的數字聲音所用采樣率

    50k Hz - 商用數字錄音機所用采樣率

    96k 或者 192k Hz - DVD-Audio、一些 LPCM DVD 音軌、BD-ROM(藍CD光牒)音軌、和 HD-DVD (高清晰度 DVD)音軌所用所用采樣率

    3、位深

    采樣率可以了解為在時間範疇上對模拟信号的量化,如上面X軸上采樣數,而位深表示數量上對模拟信号的量化,如上面Y軸的值。例如Android上常用的位深為8位,表示某個采樣點上的值,可以用8位進行表示,共有256個值的可能。而聲音資料的儲存也就是儲存每個聲音的位數。

    4、聲道數

比較常見的概念,例如單聲道(mono),立體聲(stereo),5.1聲道,7.1聲道等等。多聲道以為在采樣時候需要同時采樣每個聲道,這樣就會增加資料量。

5、資料大小

通過對采樣率、位深、聲道數的了解,可以得到下面公式:

  • 資料大小=采樣率x聲道數x(位深÷8)x時長,機關為Byte。
  • 采樣數 = 采樣率 × 音頻時長(s)× 聲道數
  • 音頻時長= 采樣數 ÷ 采樣率 ÷ 聲道數,機關為s。

6、編碼格式

在存儲和傳輸過程,通常不使用PCM格式,雖然無損以及可以直接被聲霸卡和DAC解析,但存儲大小過大,會造成本地和網絡資源的浪費。是以通常會進一步編碼,進行壓縮和添加額外的資訊,這就衍生出其他編碼格式,如MP3、AAC、FLAC等。

經過編碼後的音頻資料,一般包含三種資訊:編碼格式、容器格式、中繼資料。

編碼格式

編碼格式分為有損編碼和無損編碼格式,PCM格式是無損的,而MP3則是有損的。編碼後的音頻資料不再表示波形采樣值,不能直接通過硬體播放,需要通過編解碼器解碼後才能正常播放。

容器格式

編碼後的音頻需要和中繼資料需要一起進行容器封裝,才形成最終的音頻檔案。容器格式一般和檔案名稱字尾保持一緻。例如.mp3、.wav。編碼格式與容器格式是互相獨立的,也就說,MP3編碼的音頻是可以用WAV進行封裝。

中繼資料

在容器中用于描述音頻資料,輔助編解碼,如采樣率、長度等。

相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】

音視訊免費學習位址:https://xxetb.xet.tech/s/2cGd0

【免費分享】音視訊學習資料包、大廠面試題、技術視訊和學習路線圖,資料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以點選788280672加群免費領取~

Android音頻基礎與錄制播放

二、AudioRecord與AudioTrack使用

1. AudioRecord實作錄音

在建立AudioRecord執行個體前,我們需要通過getMinBufferSize函數來擷取目前AudioRecord可以使用的最小緩沖區大小。

getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat)           

其中sampleRateInHz為采樣率,channelConfig為聲道,audioFormat位深。

  • sampleRateInHz采樣率,因為我們這裡是錄音,是以采用44.1kHz。可以根據需要選擇其他采樣率。
  • channelConfig聲道數,這裡選擇單聲道AudioFormat.CHANNEL_IN_MONO,而立體聲為CHANNEL_IN_STEREO。其他聲道可以根據參數結合配置。
Android音頻基礎與錄制播放
  • audioFormat 編碼位深,這裡選擇預設PCM16位編碼,AudioFormat.ENCODING_PCM_16BIT。具體根據業務選擇不同的編碼位深。

接下來是建立AudioRecord執行個體:

//采樣率 44.1kHz
private val sampleRate = 44100

//聲道 單聲道
private val channel = AudioFormat.CHANNEL_IN_MONO

//位深
private val pcmBit = AudioFormat.ENCODING_PCM_16BIT

//擷取最小緩沖區大小
bufferSize = AudioRecord.getMinBufferSize(
    sampleRate, channel, pcmBit
)

//AudioRecord執行個體
audioRecord = AudioRecord(
    MediaRecorder.AudioSource.MIC, //音頻來源,麥克風
    sampleRate,
    channel,
    pcmBit,
    bufferSize
)           

這樣就可以通過AudioRecord執行個體進行錄音,下面代碼将錄音内容儲存到檔案中。

private fun startRecording() {
    if (isRecording) return
    isRecording = true
  	//開始錄音
    audioRecord.startRecording()
    GlobalScope.launch(Dispatchers.IO) {
        val buffer = ByteArray(bufferSize)
        val file = application.externalCacheDir
        val saveFile = File(file, "audio_${System.currentTimeMillis()}.pcm")
        val ous = FileOutputStream(saveFile)
        while (isRecording) {
            //音頻資料寫入緩沖區buffer
            val result = audioRecord.read(buffer, 0, bufferSize)
            //将緩沖區buffer資料寫入本地檔案
            ous.write(buffer)
        }
        isRecording = false
        ous.close()
        audioRecord.stop()
    }
}           

2. AudioTrack實作音頻播放

通過AudioRecord錄制到的音頻緩存在本地,我們通過AudioTrack将本地音頻檔案讀取出來進行播放。

建立AudioTrack執行個體,大多數參數和AudioRecord的建立一緻。

audioTrack = AudioTrack.Builder()
    .setAudioAttributes(
        AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build()
    )
    .setAudioFormat(
        AudioFormat.Builder()
            .setEncoding(pcmBit)
            .setSampleRate(sampleRate)
            .setChannelMask(channel)
            .build()
    )
    .setBufferSizeInBytes(bufferSize)
    .build()           

其中參數channel要改成AudioFormat.CHANNEL_OUT_MONO,其他可以保持一緻。然後利用AudioTrack執行個體讀取本地音頻進行播放。

fun startPlay(path: String) {
    if (isPlaying) return
    val sourceFile = File(externalCacheDir,path)
    if (sourceFile.exists()) {
        isPlaying = true
         GlobalScope.launch(Dispatchers.IO) {
           //開始播放音頻
           audioTrack!!.play()
           val fileInputStream = FileInputStream(sourceFile)
           val buffer = ByteArray(bufferSize)
            while (isPlaying) {
            	//将音頻檔案讀取緩沖區buffer
                val size = fileInputStream.read(buffer, 0, bufferSize)
                if (size <= 0) {
                    isPlaying = false
                    continue
                }
                //将緩沖區buffer寫入audioTrack進行播放
                audioTrack.write(buffer, 0, bufferSize)
            }
            audioTrack.stop()
        }
    } else {
        showToast("目前檔案不存在:${path}")
    }
}
           

通過代碼可以看到,音頻通過AudioRecord錄制和AudioTrack播放是反着來的。同時通過AudioRecord和AudioTrack可以看出,作為Android開發者,更多是調用頂層API,配置相關參數,然後交給底層進行錄制和播放。是以要熟悉相關音頻概念,以便更好的配置參數。

Github Demo路徑,包名: com.xxm.mediacodecdemo.audio

本文主要參考學習文章:

Android音頻開發(1):基礎知識

硬核音頻系列

脈沖編碼調制

原文 Android音頻基礎與錄制播放 - 掘金

繼續閱讀