一、申請權限
//攝像頭權限
<uses-permission android:name="android.permission.CAMERA" />
//文件存儲權限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//錄製權限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
二、錄像工具類
package com.lkl22.demo.util
import android.media.MediaRecorder
import android.view.SurfaceView
class VideoRecorderUtils {
companion object {
private const val TAG = "VideoRecorderUtils"
}
private var mediaRecorder: MediaRecorder? = null
private var lastFileName: String? = null
var isRecording = false
/**
* 開始錄製
* @param surfaceView 預覽需要的SurfaceView
* @return 錄製完的video的全路徑
*/
fun startRecording(surfaceView: SurfaceView): String {
// 創建mediarecorder對象
mediaRecorder = MediaRecorder()
mediaRecorder?.apply {
// 設置錄製視頻源爲Camera(相機)
setVideoSource(MediaRecorder.VideoSource.CAMERA)
// 設置錄製完成後視頻的封裝格式THREE_GPP爲3gp.MPEG_4爲mp4
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
// 設置錄製的視頻編碼h263 h264
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
// 設置視頻錄製的分辨率。必須放在設置編碼和格式的後面,否則報錯
setVideoSize(800, 600)
// 設置錄製的視頻幀率。必須放在設置編碼和格式的後面,否則報錯
setVideoFrameRate(20)
setPreviewDisplay(surfaceView.holder.surface)
// 設置視頻文件輸出的路徑
lastFileName = newFileName()
setOutputFile(lastFileName)
try {
// 準備錄製
prepare()
isRecording = true
// 開始錄製
start()
} catch (e: Exception) {
LogUtils.e(TAG, e)
isRecording = false
}
}
return lastFileName ?: ""
}
private fun newFileName(): String {
return FileUtils.videoDir + DateUtils.nowTime + ".3gp"
}
/**
* 停止錄像
*/
fun stopRecording() {
isRecording = false
mediaRecorder?.apply {
stop()
release()
mediaRecorder = null
}
}
}
三、抽幀保存圖片工具類
package com.lkl22.demo.util
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.net.Uri
import com.lkl22.demo.MyApplication.Companion.context
import java.io.File
import java.io.FileOutputStream
object BitmapUtils {
private const val TAG = "BitmapUtils"
/**
* 抽取視頻的第一幀圖片並保存到本地
* @param uri 視頻文件的全路徑
* @return 抽取的第一幀圖片保存的全路徑
*/
fun saveFrameBitmap(uri: String): String? {
val bitmap = getFrameBitmap(uri) ?: return null
val fileName = uri.substringAfterLast("/").substringBeforeLast(".")
return saveBitmap(FileUtils.bitmapDir + fileName + ".png", bitmap)
}
/**
* 獲取網絡/本地視頻的第一幀圖片
* @param url 視頻文件的url地址
* @param isSd 是否是本地視頻文件
* @return 第一幀圖片
*/
fun getFrameBitmap(uri: String, isSd: Boolean = true): Bitmap? {
var bitmap: Bitmap? = null
//MediaMetadataRetriever 是android中定義好的一個類,提供了統一的接口,用於從輸入的媒體文件中取得幀和元數據
val retriever = MediaMetadataRetriever()
try {
if (isSd) {
//()根據文件路徑獲取縮略圖
retriever.setDataSource(context, Uri.fromFile(File(uri)))
} else {
//根據網絡路徑獲取縮略圖
retriever.setDataSource(uri, HashMap())
}
//獲得第一幀圖片
bitmap = retriever.frameAtTime
} catch (e: IllegalArgumentException) {
LogUtils.e(TAG, e)
} finally {
retriever.release()
}
return bitmap
}
/**
* 保存圖片到本地
* @param uri 要保存的全路徑
* @param bitmap 圖片文件
* @return null 保存失敗 否則返回保存成功後的全路徑
*/
fun saveBitmap(uri: String?, bitmap: Bitmap): String? {
if (uri.isNullOrEmpty()) {
return null
}
val f = File(uri)
if (f.exists()) {
f.delete()
}
return try {
val out = FileOutputStream(f)
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out)
out.flush()
out.close()
uri
} catch (e: Exception) {
LogUtils.e(TAG, e)
null
}
}
}
四、使用
4.1 配置SurfaceView
var surfaceHolder = surfaceView.holder
surfaceHolder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
surfaceHolder = holder
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
surfaceHolder = null
}
override fun surfaceCreated(holder: SurfaceHolder?) {
surfaceHolder = holder
}
})
//使用該類型也可以不用,已經廢棄掉的方法,兼容比較低的版本可以加上
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
4.2 錄製功能
//啓動錄製
val fileUri = mVideoRecorderUtils.startRecording(surfaceView)
//停止錄製
mVideoRecorderUtils.stopRecording()
//抽取第一幀圖片並保存,該方法一定要在錄製完成後去調用
saveFrameBitmap(uri)
五、參考文獻
MediaRecorder overview
MediaMetadataRetriever 從輸入媒體文件檢索幀和元數據