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 仿微信錄製短視頻

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