1. 程式人生 > >Android 仿微信錄製短視訊(不使用 FFmpeg)

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([email protected], 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 仿微信錄製短視訊