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 从输入媒体文件检索帧和元数据

你可能感兴趣的:(Android精华教程)