Android錄製video並抽取第一幀圖片

一、申請權限

//攝像頭權限
<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 從輸入媒體文件檢索幀和元數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章