Android音視頻入門(一):音頻的錄制和播放
一、前言
當(dāng)我們使用各種播放器,系統(tǒng)API來完成音視頻播放和錄制的時候,其實底層已經(jīng)幫我做了很多看不到的工作。比如如何進行采集、編解碼、合成、壓縮等一系列工作,各種庫其實都已經(jīng)幫我實現(xiàn)了,我們只需要按規(guī)范調(diào)用API來完成我們想要的功能。但是在未來的學(xué)習(xí)過程中我們會一一的深入內(nèi)部去探討各個知識點。
下面是整理的關(guān)于android音視頻開發(fā)的學(xué)習(xí)腦圖,分別把采集、渲染、編碼、傳輸相關(guān)的知識點拆分了下,通過這個圖可以大概的了解目前的學(xué)習(xí)位置。


demo截屏


二、使用系統(tǒng)API完成音視頻采集和播放
2.1 播放音視頻文件
在android開發(fā)中系統(tǒng)的api已經(jīng)提供了一個叫MediaRecorder的類給開發(fā)者來完成音視頻的播放,底層的一系列細(xì)節(jié)都被封裝了起來,所以我們現(xiàn)在使用api完成體驗一下成功的感覺。
2.1.1 MediaPlayer 概覽
Android 多媒體框架支持播放各種常見媒體類型,以便您輕松地將音頻、視頻和圖片集成到應(yīng)用中。您可以使用 MediaPlayer API,播放存儲在應(yīng)用資源(原始資源)內(nèi)的媒體文件、文件系統(tǒng)中的獨立文件或者通過網(wǎng)絡(luò)連接獲得的數(shù)據(jù)流中的音頻或視頻。
下面我們使用MediaPlayer來完成音視頻的播放,播放源支持網(wǎng)絡(luò)文件和本地文件。
配置清單文件的權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
//喚醒鎖定權(quán)限 - 如果播放器應(yīng)用需要防止屏幕變暗或處理器進入休眠狀態(tài),或者要使用 MediaPlayer.setScreenOnWhilePlaying() 或 MediaPlayer.setWakeMode() 方法,則您必須申請此權(quán)限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
部分核心代碼
//播放視頻
var mediaPlayer: MediaPlayer? = MediaPlayer()
private fun startMedia() {
if (!mediaPlayer?.isPlaying!!) {
//設(shè)置播放源 支持本地文件和網(wǎng)絡(luò)文件
mediaPlayer?.setDataSource("https://rbv01.ku6.com/wifi/o_1dv2knrei18ju12l7l2vgj210oe11kvs")
//開始異步準(zhǔn)備
mediaPlayer?.prepareAsync()
//設(shè)置監(jiān)聽
mediaPlayer?.setOnPreparedListener {
//開始播放
mediaPlayer?.start()
}
}
}
//播放音頻
private fun startAudio() {
if (!mediaPlayer?.isPlaying!!) {
val path = "$filesDir/record_mp3/vick.aac"
mediaPlayer?.setDataSource(this, Uri.parse(path))
mediaPlayer?.prepareAsync()
mediaPlayer?.setOnPreparedListener {
mediaPlayer?.start()
}
}
}
2.1.2 AudioTrack 概覽
AudioTrack只支持wav格式的音頻文件,因為wav格式的音頻文件大部分都是PCM流。AudioTrack不創(chuàng)建解碼器,所以只能播放不需要解碼的wav文件。
MediaPlayer在framework層還是會創(chuàng)建AudioTrack,把解碼后的PCM數(shù)流傳遞給AudioTrack,AudioTrack再傳遞給AudioFlinger進行混音,然后才傳遞給硬件播放,所以是MediaPlayer包含了AudioTrack。
部分核心代碼
class MeAudioTrack : PlayerAudioI {
private var audioData: ByteArray? = null
var audioTrack: AudioTrack? = null
override fun initData() {
//通過 write 寫文件
}
override fun start(filePath: String) {
//讀文件
stop()
// play1(filePath)
playInModeStream(filePath)
}
var mHandler: Handler = Handler()
var mRunnable: Runnable = Runnable {
playInModeStatic()
}
/**
* 播放,使用stream模式
*/
private fun playInModeStream(path: String) {
/*
* SAMPLE_RATE_INHZ 對應(yīng)pcm音頻的采樣率
* channelConfig 對應(yīng)pcm音頻的聲道
* AUDIO_FORMAT 對應(yīng)pcm音頻的格式
* */
val channelConfig = AudioFormat.CHANNEL_OUT_MONO
val minBufferSize =
AudioTrack.getMinBufferSize(SAMPLE_RATE_INHZ, channelConfig, AUDIO_FORMAT)
audioTrack = AudioTrack(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
AudioFormat.Builder().setSampleRate(SAMPLE_RATE_INHZ)
.setEncoding(AUDIO_FORMAT)
.setChannelMask(channelConfig)
.build(),
minBufferSize,
AudioTrack.MODE_STREAM,
AudioManager.AUDIO_SESSION_ID_GENERATE
)
audioTrack?.play()
val file = File(path)
try {
val fileInputStream = FileInputStream(file)
Thread(Runnable {
try {
val tempBuffer = ByteArray(minBufferSize)
while (fileInputStream.available() > 0) {
val readCount = fileInputStream.read(tempBuffer)
if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {
continue
}
if (readCount != 0 && readCount != -1) {
audioTrack?.write(tempBuffer, 0, readCount)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}).start()
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun playInModeStatic() {
audioTrack = AudioTrack(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build(),
AudioFormat.Builder().setSampleRate(22050)
.setEncoding(AudioFormat.ENCODING_PCM_8BIT)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
audioData?.size!!,
AudioTrack.MODE_STATIC,
AudioManager.AUDIO_SESSION_ID_GENERATE
)
audioTrack?.write(audioData, 0, audioData?.size!!)
audioTrack?.play()
}
private fun play1(path: String) {
// static模式,需要將音頻數(shù)據(jù)一次性write到AudioTrack的內(nèi)部緩沖區(qū)
Thread {
audioData = FileUtils.readFile2Bytes(path)
mHandler.post(mRunnable)
}.run { start() }
}
override fun pause() {
}
override fun stop() {
audioTrack?.stop()
audioTrack?.release()
}
}
2.2 采集音視頻
Android提供了兩套音頻采集的API,MediaRecorder 和 AudioRecord前者是一個更加上層一點的API。
2.2.1 MediaRecorder
MediaRecorder 概覽
MediaRecorder類似于mediaPlayer是一個偏向上層的api,支持音頻的采集和視頻的采集。
部分核心代碼
private var mediaRecorder: MediaRecorder? = null
override fun start() {
mediaRecorder = MediaRecorder()
//輸出目錄
mediaRecorder?.setOutputFile(getPath())
//設(shè)置采集源
mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
//設(shè)置輸出格式 3GPP media file format
mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
//編解碼的格式 AMR (Narrowband) audio codec
mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
mediaRecorder?.prepare()
mediaRecorder?.start()
}
override fun stop() {
mediaRecorder?.stop()
mediaRecorder?.release()
mediaRecorder = null
}
private fun getPath(): String {
val path =
mContext?.getExternalFilesDir("media").toString() + File.separator
mFileName = System.currentTimeMillis().toString() + ".aac"
Log.i("VICK", path + mFileName)
mFilePath = path + mFileName
return mFilePath
}
2.2.2 AudioRecord采集
部分核心代碼
class AudioRecorder : AudioRecorderI {
/**
* 采樣率,現(xiàn)在能夠保證在所有設(shè)備上使用的采樣率是44100Hz, 但是其他的采樣率(22050, 16000, 11025)在一些設(shè)備上也可以使用。
*/
val SAMPLE_RATE_INHZ = 44100
/**
* 聲道數(shù)。CHANNEL_IN_MONO and CHANNEL_IN_STEREO. 其中CHANNEL_IN_MONO是可以保證在所有設(shè)備能夠使用的。
*/
val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
/**
* 返回的音頻數(shù)據(jù)的格式。 ENCODING_PCM_8BIT, ENCODING_PCM_16BIT, and ENCODING_PCM_FLOAT.
*/
val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
var isRecord = false
var mContext: Context? = null
override fun getFilePath(): String {
return mFilePath
}
var mFileName = ""
var mFilePath = ""
override fun getFileName(): String {
return mFileName
}
override fun initData(context: Context) {
mContext = context
}
var audioRecord: AudioRecord? = null
override fun start() {
//獲得最小緩沖區(qū)大小,用于記錄音頻所用。
val minBufferSize = getMinBufferSize(
44100,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)
//構(gòu)建錄音對象
audioRecord = AudioRecord(
MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT, minBufferSize
)
mFileName = System.currentTimeMillis().toString() + ".acc"
val byteArray = ByteArray(minBufferSize)
val file = File(mContext?.getExternalFilesDir("media"), mFileName)
mFilePath = file.toString()
audioRecord?.startRecording()
isRecord = true
//開啟線程寫入錄音數(shù)據(jù)
Thread {
var outputStream: FileOutputStream? = null
try {
outputStream = FileOutputStream(file)
while (isRecord) {
//audioRecord通過 read() 方法來讀取數(shù)據(jù)
val read = audioRecord?.read(byteArray, 0, minBufferSize)
//不出錯就寫入文件
if (AudioRecord.ERROR_INVALID_OPERATION != read) {
outputStream.write(byteArray)
}
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
outputStream?.close()
}
}.start()
}
override fun pause() {
}
override fun stop() {
isRecord = false
audioRecord?.stop()
audioRecord?.release()
audioRecord = null
}
override fun destroy() {
}
}
2.2.3 采集相機數(shù)據(jù)
SurfaceView或TextureView采集相機數(shù)據(jù)
class CmaraActivity : AppCompatActivity(), SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
camera?.setPreviewDisplay(holder)
camera?.startPreview()
}
var surfaceView: SurfaceView? = null
var textureView: TextureView? = null
var camera: android.hardware.Camera? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.camera_activity)
surfaceView = findViewById(R.id.surface)
textureView = findViewById(R.id.texture)
camera = android.hardware.Camera.open()
camera?.setDisplayOrientation(90)
val parameters = camera?.parameters
parameters?.pictureFormat = ImageFormat.NV21
camera?.parameters = parameters
camera?.setPreviewCallback(object : android.hardware.Camera.PreviewCallback {
override fun onPreviewFrame(data: ByteArray?, camera: android.hardware.Camera?) {
Log.i("vick", data.toString())
}
})
// surfaceView?.holder?.addCallback(this)
textureView?.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureSizeChanged(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
camera?.release()
return false
}
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture?,
width: Int,
height: Int
) {
camera?.setPreviewTexture(surface)
camera?.startPreview()
}
}
}
}
三、總結(jié)
播放
目前知道系統(tǒng)提供給開發(fā)者的api有MediaPlayer一個高度封裝的類,可以播放器本地音視頻文件或者網(wǎng)絡(luò)文件,使用AudioTrack可以播放原始的音頻文件。
采集
MediaRecorder和AudioRecord可以采集音頻文件,MediaRecorder 底層封裝了AudioRecord,比如直播需要傳輸?shù)脑捑托枰盟鼇碜觥?/p>