Android 仿微信錄製短視訊(不使用 FFmpeg)
轉載請標明出處與作者:https://www.jianshu.com/p/2cb7b0110fde
專案中原本就有錄製短視訊的功能,使用的是 # qdrzwd/VideoRecorder 這個專案,但是該專案不支援 targetSdkVersion 22以上的版本,而現在各大市場都要求 targetSdkVersion 必須要26以上了,所以急需找到替代的方案。
分析
解決方法大致上有如下四種:
- 使用 FFmpeg
- 使用系統攝像頭
- 使用 MediaRecorder
- 使用阿里雲、騰訊雲、七牛雲等短視訊服務
其中方案一可以參考:
利用FFmpeg玩轉Android視訊錄製與壓縮(一)
利用FFmpeg玩轉Android視訊錄製與壓縮(二)
利用FFmpeg玩轉Android視訊錄製與壓縮(三)
編譯Android下可執行命令的FFmpeg
編譯Android下可用的全平臺FFmpeg(包含libx264與libfdk-aac)
Android下玩JNI的新老三種姿勢
Github 上專案地址為:# mabeijianxi/small-video-record
該專案存在一些問題,我在使用小米6測試其 Demo 時,既不能錄影也不能選取本地視訊進行壓縮。另外引入 FFmpeg 對於本需求而言,時間成本、學習成本、APK 最終體積增量都是不划算的選擇。
方案二大概是最簡單與穩定可靠(機型適配方面)的了:
var intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
//設定視訊錄製的最長時間
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10)
//設定視訊錄製的畫質
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1)
startActivityForResult(intent, VIDEO_WITH_CAMERA)
但是存在著一個致命的缺點,錄製完的視訊體積非常大,對畫質配置只有 1、0 這兩種選擇。其中 1 最終成片體積太大,0 畫質太渣,基本不可用。
方案四就不多提了,我們的專案並不是專門的短視訊 APP,使用這些付費 SDK 完全是殺雞用牛刀。
最終決定通過方案三,使用 MediaRecorder 來完成了該功能,該方案具有以下優勢:
- 無需引入任何第三方庫,不會增加 APK 體積
- 系統自帶功能,幾乎不存在機型設配問題
- 最終成片引數可控(解析度、幀數、編碼位元率)
致謝:本文程式碼大量參考了[胖子愛你520
](https://blog.csdn.net/woshizisezise) 所寫 Android使用MediaRecorder和Camera實現視訊錄製及播放功能整理 一文,並對其程式碼進行了功能上的優化與 UI 上的美化。
預覽:
測試手機為 小米6,最終 10s 短視訊成片體積在3M左右,處於可接受範圍。
功能實現
警告⚠️:以下內容還有大量 Kotlin 程式碼,可能會引起不適。
此處我們只談及一些關鍵的程式碼片段,完整工程請移步 # junerver/VideoRecorder
,如果對您有幫助,請 star ,歡迎反饋問題,我會盡量維護更新。
錄影是如何實現的?
1.啟動錄製⏺
mRecorder = MediaRecorder()
mRecorder?.reset()
mRecorder?.setCamera(mCamera) //分配攝像頭
// 視訊音訊源
mRecorder?.setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
mRecorder?.setVideoSource(MediaRecorder.VideoSource.CAMERA)
// 輸出檔案格式
mRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
// 編碼器 注意,如果使用AMR_NB將會導致IOS無法播放
mRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
mRecorder?.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP)
mRecorder?.setVideoSize(640, 480) //輸出視訊的解析度
mRecorder?.setVideoFrameRate(30) //幀率
mRecorder?.setVideoEncodingBitRate(3 * 1024 * 1024) //編碼位元率
mRecorder?.setOrientationHint(90)
//設定記錄會話的最大持續時間(毫秒)
mRecorder?.setMaxDuration(30 * 1000)
path = AppConfig.VIDEO_FILE
if (path != null) {
var dir = File(path)
if (!dir.exists()) {
dir.mkdir()
}
path = dir.absolutePath + "/" + getDate() + ".mp4"
mRecorder?.setOutputFile(path)
mRecorder?.prepare()
mRecorder?.start()
}
2.結束錄製⏺
mRecorder?.stop() //結束錄製
mRecorder?.reset() //重置
mRecorder?.release() //釋放資源
以上就是錄製視訊的最核心的程式碼了,可見,首先我們需要為 MediaRecorder 分配一個攝像頭,然後配置相關屬性,在最後結束時呼叫 stop()
方法即可。
重要:在分配攝像頭資源(MediaRecorder.setCamera(mCamera)
)之前,必須先解鎖攝像頭(mCamera.unlock()
),否則會提示 MediaRecorder: start failed: -19
優化體驗
如果你看過上文我們所提到的那篇文章,會發現按照他的程式碼實現的話,在開始錄製視訊之前是沒有畫面的(關鍵程式碼 mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
),只有使用者點選了錄製按鈕,開始錄製之後才會有攝像頭的預覽畫面,這無疑是不合理的。
而 MediaRecorder 在錄製視訊的過程中該操作並不是必要操作,那麼我們完全可以使用攝像頭的預覽畫面來填充到我們的 SurfacerView 中來,這樣整個體驗就非常流暢了。
var holder = mSurfaceview.holder
holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
mSurfaceHolder = holder!!
mCamera?.startPreview()
mCamera?.cancelAutoFocus()
// 關鍵程式碼 該操作必須在開啟預覽之後進行(最後呼叫),
// 否則會黑屏,並提示該操作的下一步出錯
// 只有執行該步驟後才可以使用MediaRecorder進行錄製
// 否則會報 MediaRecorder(13280): start failed: -19
mCamera?.unlock()
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
try {
mSurfaceHolder = holder!!
//使用後置攝像頭
mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK)
//旋轉攝像頭90度
mCamera?.setDisplayOrientation(90)
mCamera?.setPreviewDisplay(holder)//將攝像頭預覽畫面填充到SurfaceView
val parameters = mCamera?.parameters
parameters?.pictureFormat = PixelFormat.JPEG
parameters?.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE//1連續對焦
mCamera?.parameters = parameters
} catch (e: RuntimeException) {
//Camera.open() 在攝像頭服務無法連線時可能會丟擲 RuntimeException
showToast("開啟攝像頭失敗,請稍後再試!")
finish()
}
}
})
拍攝完成成片預覽
此處沒什麼可說的,直接呼叫系統提供的 MediaPlayer 即可
mMediaPlayer?.reset()
var uri = Uri.parse(path)
mMediaPlayer = MediaPlayer.create([email protected], uri)
mMediaPlayer?.setAudioStreamType(AudioManager.STREAM_MUSIC)
mMediaPlayer?.setDisplay(mSurfaceHolder)
mMediaPlayer?.setOnCompletionListener {
//播放解釋後再次顯示播放按鈕
mBtnPlay.visibility =View.VISIBLE
}
try{
mMediaPlayer?.prepare()
}catch (e:Exception){
e.printStackTrace()
}
mMediaPlayer?.start()
案例下載地址:Android 仿微信錄製短視訊