Android 仿微信录制短视频(不使用 FFmpeg)

转载请标明出处与作者:https://www.jianshu.com/p/2cb7b0110fde

项目中原本就有录制短视频的功能,使用的是 # qdrzwd/VideoRecorder 这个项目,但是该项目不支持 targetSdkVersion 22以上的版本,而现在各大市场都要求 targetSdkVersion 必须要26以上了,所以急需找到替代的方案。

分析

解决方法大致上有如下四种:

  1. 使用 FFmpeg
  2. 使用系统摄像头
  3. 使用 MediaRecorder
  4. 使用阿里云、腾讯云、七牛云等短视频服务

其中方案一可以参考:
利用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 来完成了该功能,该方案具有以下优势:

  1. 无需引入任何第三方库,不会增加 APK 体积
  2. 系统自带功能,几乎不存在机型设配问题
  3. 最终成片参数可控(分辨率、帧数、编码比特率)

致谢:本文代码大量参考了[胖子爱你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(VideoRecordActivity@this, 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 仿微信录制短视频

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