一、音頻概覽
聲音是振動産生的能量(聲波),通過媒體傳播并能被人或動物聽覺器官所感覺的波動現象。例如人類說話時通過聲帶振動産生聲波,通過空氣傳播後到達其他人耳朵。
要采集聲音,就需要通過麥克風對聲音進行錄制。錄音就是将聲音轉為模拟信号或機械記錄的過程。而數字錄音是進一步将模拟信号經由ADC(模拟數字轉換器)将類比取樣成數字記錄到儲存設備。
在Android開發音頻,就是利用AudioRecoder、MediaRecoder錄音,通過AudioTrack、MediaPlayer播放聲音,而這一過程會涉及到很多知識點,例如錄制聲音過程的采樣率、位深、編解碼MediaCodec、FFmpeg等等。
總結來說:
- 音頻錄制與播放(Android相關類的使用,Framework調用原理)
- 音頻處理(去噪、靜音檢測、回聲消除、音效處理、功放/增強、混音分離)
- 音頻的編解碼和格式轉換(MediaCodec、FFmpeg)
- 音頻傳輸協定(SIP\A2DP、AVRCP等)
1、PCM
PCM全名叫脈沖編碼調制,是一種将模拟信号數字化的方法。PCM将信号的強度依照同樣的間距分成數段,然後使用獨特的數字記号(二進制)量化。這一過程通常由ADC來實作。作為Android程式員,更多是調用AudioTrack錄制音頻後,儲存為PCM編碼格式,也可以再進一步編碼,存儲成其它格式。
例如下圖(正常應該是正弦波):
用綠色圓圈來表示連續的模拟信号,而紅色圓點則表示數字信号。PCM過程就是在模拟信号上進行采樣和收集,形成斷斷續續的采樣點,也就是數字信号。在X軸方向上,可以了解為對音頻的量化是時間,展現在采樣率上。在Y軸方向,可以了解為量化是數量,展現為位深。
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加群免費領取~
二、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。其他聲道可以根據參數結合配置。
- 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音頻基礎與錄制播放 - 掘金