基于Camera2和MediaRecorder实现视频录制

一、概述

视频录制,在一般开发中很少遇到,大部分开发工作都是写写页面,请求接口,展示数据等等。真要遇到,可能采用第三方库实现,一来实现快速,二来可能觉得别人实现的比较好。特别是在开发周期很紧的情况下,一般都不会自己花时间实现。

其实最好是使用手机系统的录制视频,功能完善,稳定。实现起来最简单,简简单单几句代码:

  //跳转系统的录制视频页面
  val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
  intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY,1)
  intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,30)//录制时长
  startActivityForResult(intent, 666)

   //打开手机的选择视频页
//  val intent = Intent()
//  intent.action = Intent.ACTION_PICK
//  intent.data = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
//  startActivityForResult(intent,666)

然后在onActiityResult方法中接收录制好的视频路径处理即可。

但如果需求是像微信那样app内的录制视频,就不能使用系统自带录制功能,需要自己实现。

下面将我自己实现的,记录下来。这里也只是实现了一个简单的录制功能,甚至还有问题:前置摄像头录制视频是镜像的。
另外下面的实现不支持在Android6.0以下的手机上使用,因为使用到了API23的方法:MediaCodec.createPersistentInputSurface(),主要是为了能支持横屏录制的视频方向为横屏。

先看看演示效果:
在这里插入图片描述

二、实现方案和细节

使用的Camera2 和 MediaRecorder。
如果使用Camera1的话,可能会更简单一些,Camera2用起来确实相对麻烦一点。不过Camera1毕竟已经被弃用了,且使用Camera1打开相机比Camera2要耗时一些。

Camera2使用

  1. 用CameraManager获取相机Id列表cameraIdList,然后openCamera指定的相机id,打开相机
  2. 打开成功后,使用 CameraDevice.createCaptureSession 创建CameraCaptureSession
  3. 创建成功后,使用CameraCaptureSession.setRepeatingRequest 发起预览请求,它需要传入CaptureRequest,通过CameraDevice.captureRequest创建,CaptureRequest可以设置一些参数,对焦、曝光、闪光灯等等

第2步 createCaptureSession 时需要传入Surface列表。

这里传入了两个Surface,一个是预览使用,由SurfaceView提供。
另一个是录制使用,通过MediaCodec.createPersistentInputSurface() 创建,设置给MediaRecorder。
如果预览时不创建MediaRecorder,只传入预览Surface,等到点击录制时,才创建MediaRecorder,需要重新创建createCaptureSession,传入新的Surface,这样虽然可以,但是点击录制时会很慢,预览画面会断一下。

第2步传入的Surface列表,还需要在第3步中使用CaptureRequest.addTarget 添加,两个地方必须对应,不然发起预览请求时会报错。

MediaRecorder配置

因为使用的是Camera2,所以不能使用MediaRecorder.setCamera()。
替代方法是使用MediaRecorder.surface,前提是需要设置数据源为Surface

mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)

且需要在mediaRecorder.prepare之后,mediaRecorder.surface 才可用。

后面因为要支持横屏录制,没有采用 mediaRecorder.surface 。
如果进入页面开启相机预览时手机竖屏,点击录制时手机横屏,因为在预览时就创建了mediaRecorder,并且setOrientationHint确定了视频方向,无法再改变(只能prepare之前设置),这时录制的视频方向肯定就不对。

要改变视频方向,只能重新创建 mediaRecorder ,但是重新创建mediaRecorder,同时也重新创建了一个新的Sueface,需要重新createCaptureSession传入新的Sueface。(改成点击录制时,创建mediaRecorder,然后重新createCaptureSession,测试中也发现画面会断一下,效果不好)。

正因如此,最终改为使用 MediaCodec.createPersistentInputSurface() 创建 Surface,然后 setInputSurface 给 mediaRecorder。MediaCodec.createPersistentInputSurface()创建的Surface只有在mediaRecorder.prepare之后才可用。 在点击录制时,重新配置mediaRecorder,设置新的方向。这样虽然mediaRecorder重新配置了,但是Surface还是同一个。

		var mediaRecorder = MediaRecorder()
        recordSurface = MediaCodec.createPersistentInputSurface()
        var recordSurface = mRecordSurface
        if (recordSurface == null) {
    
    
            recordSurface = MediaCodec.createPersistentInputSurface()
            mRecordSurface = recordSurface
        }
        mediaRecorder.setInputSurface(recordSurface)
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
        //数据源来之surface
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
        //设置QUALITY_HIGH,可以提高视频的录制质量(文件也会变大),但是不能设置编码格式和帧率等参数,否则报错
        val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P)
        mediaRecorder.setProfile(profile)
        mediaRecorder.setMaxFileSize(0)
        mediaRecorder.setVideoSize(width, height)
        //视频方向
        mediaRecorder.setOrientationHint(mRecordVideoOrientation)
        val parentFile = externalCacheDir ?: cacheDir
        val fileName = "${
      
      System.currentTimeMillis()}.mp4"
        //不设置setOutputFile prepare时会报错
        mediaRecorder.setOutputFile(parentFile.absolutePath + File.separator + fileName)
        //prepare之后recordSurface才能用
        mediaRecorder.prepare()

三、全部代码

/**
 * 录制视频
 * 支持后置、前置摄像头切换(但前置摄像头录制视频是镜像的,需要翻转) TODO
 * Android 6.0以下不支持
 * 支持横屏录制
 * 2023/03/17
 */
@RequiresApi(Build.VERSION_CODES.M)
class VideoRecord23Activity : AppCompatActivity(), SurfaceHolder.Callback,
    View.OnClickListener {
    
    

    companion object {
    
    
        const val DURATION = "duration"
        const val FILE_NAME = "name"
        const val FILE_PATH = "path"
    }
    private val requestPermissionCode = 52
    private val requestActivityCode = 10

    /**
     * mCountDownMsg 录制进度倒计时msg,一秒钟发送一次
     * mStartRecordMsg 开始录制
     * mCameraOpenFailMsg 相机打开失败
     * mCameraPreviewFailMsg 相机预览失败
     * mRecordErrorMsg 录制出现错误
     * 在 mCountDownHandler(主线程的Handler)中处理
     */
    private val mCountDownMsg = 19
    private val mStartRecordMsg = 20
    private val mCameraOpenFailMsg = 21
    private val mCameraPreviewFailMsg = 22
    private val mRecordErrorMsg = 23
    private lateinit var mSurfaceView :SurfaceView
    private lateinit var mRecordProgressBar: ProgressBar
    private lateinit var mRecordStateIv: ImageView
    private lateinit var mFlashlightIv: ImageView
    private lateinit var mSwitchCameraIv: ImageView
    /**
     * 录制视频文件路径
     */
    @Volatile
    private var mFilePath: String? = null

    /**
     * 录制的视频文件名
     */
    @Volatile
    private var mFileName: String? = null

    /**
     * 预览画面尺寸,和视频录制尺寸
     */
    @Volatile
    private var mRecordSize: Size? = null

    /**
     * 相机方向
     */
    private var mCameraOrientation: Int = 0

    /**
     * 录制视频的方向,随着手机方向的改变而改变
     */
    @Volatile
    private var mRecordVideoOrientation: Int = 0

    /**
     * 默认打开后置相机 LENS_FACING_BACK
     * 可以切换为前置相机 LENS_FACING_FRONT
     */
    private var mFensFacing = CameraCharacteristics.LENS_FACING_BACK

    /**
     * 预览Surface
     */
    @Volatile
    private var mPreviewSurface: Surface? = null

    /**
     * 录制Surface
     */
    @Volatile
    private var mRecordSurface: Surface? = null

    @Volatile
    private var mCameraDevice: CameraDevice? = null

    @Volatile
    private var mCameraCaptureSession: CameraCaptureSession? = null

    @Volatile
    private var mCaptureRequest: CaptureRequest.Builder? = null
    private var mOrientationEventListener: OrientationEventListener? = null

    @Volatile
    private var mMediaRecorder: MediaRecorder? = null
    /**
     * 是否是录制中的状态
     * true:录制中
     */
    @Volatile
    private var mRecordingState = false

    /**
     * 是否录制完成。从手动点击开始录制到手动点击停止录制(或者录制时长倒计时到了),为录制完成,值为true。其他情况为false
     */
    private var mRecordComplete = false

    /**
     * 闪光灯状态
     * true 开启
     * false 关闭
     */
    private var mFlashlightState = false

    /**
     * 是否可以录制
     * 录制完成,跳转播放页面后,返回时点击的完成,不能录制
     * 其他情况都为可以录制
     */
    private var mRecordable = true

    /**
     * 录制最大时长,时间到了之后录制完成
     * 单位:秒
     */
    private var mMaxRecordDuration = 30

    /**
     * 已录制的时长
     */
    private var mCurrentRecordDuration = 0

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_video_record)

        mSurfaceView = findViewById(R.id.surfaceView)
        mSurfaceView.holder.addCallback(this)
        mRecordStateIv = findViewById(R.id.recordStateIv)
        mRecordProgressBar = findViewById(R.id.recordProgressBar)
        mFlashlightIv = findViewById(R.id.flashlightIv)
        mSwitchCameraIv = findViewById(R.id.switchIv)
        mRecordStateIv.setOnClickListener(this)
        mFlashlightIv.setOnClickListener(this)
        mSwitchCameraIv.setOnClickListener(this)

        initOrientationEventListener()
        mMaxRecordDuration = intent.getIntExtra(DURATION, 30)
        mSurfaceView.scaleX = -1f
    }

    override fun surfaceCreated(holder: SurfaceHolder?) {
    
    
        if (mRecordable) {
    
    
            mRecordComplete = false
            mFlashlightState = false
            mFlashlightIv.setImageResource(R.drawable.flashlight_off)
            checkPermissionAndOpenCamera()
            mOrientationEventListener?.enable()
        }
    }

    override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
    
    
    }

    override fun surfaceDestroyed(holder: SurfaceHolder?) {
    
    
    }

    @SuppressLint("MissingPermission")
    override fun onClick(v: View) {
    
    
        when (v.id) {
    
    
            R.id.recordStateIv -> {
    
    
                if (mRecordingState) {
    
    
                    //停止录制
                    stopRecord()
                    mRecordComplete = true
                    //跳转预览页面
                    openPlayActivity()
                } else {
    
    
                    startRecord()
                    mOrientationEventListener?.disable()
                }
            }
            R.id.flashlightIv -> {
    
    
                val captureRequest = mCaptureRequest ?: return
                val cameraCaptureSession = mCameraCaptureSession ?: return
                if (mFlashlightState) {
    
    
                    //闪光灯开启,点击关闭
                    captureRequest[CaptureRequest.FLASH_MODE] = CaptureRequest.FLASH_MODE_OFF
                    mFlashlightIv.setImageResource(R.drawable.flashlight_off)
                } else {
    
    
                    //闪关灯关闭,点击开启
                    captureRequest[CaptureRequest.FLASH_MODE] = CaptureRequest.FLASH_MODE_TORCH
                    mFlashlightIv.setImageResource(R.drawable.flashlight_on)
                }
                mFlashlightState = !mFlashlightState
                cameraCaptureSession.stopRepeating()
                mCameraCaptureSession?.setRepeatingRequest(captureRequest.build(), mCameraCaptureSessionCaptureCallback, mRecordThreadHandler)
            }
            R.id.switchIv -> {
    
    
                if (mRecordingState) {
    
    
                    //正在录制中
                    Toast.makeText(this, "正在录制", Toast.LENGTH_SHORT).show()
                    return
                }
                if (mFensFacing == CameraCharacteristics.LENS_FACING_BACK) {
    
    
                    //当前打开的是后置摄像头,切换到前置摄像头
                    mFensFacing = CameraCharacteristics.LENS_FACING_FRONT
                    close()
                    openCamera()
                } else if (mFensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
    
    
                    //当前打开的是前置摄像头,切换到后置摄像头
                    mFensFacing = CameraCharacteristics.LENS_FACING_BACK
                    close()
                    openCamera()
                }
            }
        }
    }

    private fun startRecord() {
    
    
        mRecordThreadHandler.sendEmptyMessage(mStartRecordMsg)
        mRecordingState = true
        mRecordStateIv.setImageResource(R.drawable.record_start_state_bg)
        mCurrentRecordDuration = 0
        //开始录制倒计时
        mUiHandler.sendEmptyMessageDelayed(mCountDownMsg, 1000)
    }

    private fun stopRecord() {
    
    
        //视图变为 停止录制状态
        mRecordingState = false
        mRecordStateIv.setImageResource(R.drawable.record_stop_state_bg)
        mRecordProgressBar.progress = 0
        mUiHandler.removeMessages(mCountDownMsg)
        val mediaRecorder = mMediaRecorder ?: return
        try {
    
    
            mediaRecorder.stop()
        } catch (t: Throwable) {
    
    
            t.printStackTrace()
        }
    }

    /**
     * 检查相机和录音与权限,并打开相机
     */
    private fun checkPermissionAndOpenCamera(){
    
    
        //录制音频权限ok
        val audioOk = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
        //相机权限ok
        val cameraOk = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED
        if (audioOk && cameraOk) {
    
    
            openCamera()
        } else if (!audioOk && !cameraOk) {
    
    
            val array = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
            ActivityCompat.requestPermissions(this, array, requestPermissionCode)
        } else if (!audioOk && cameraOk) {
    
    
            val array = arrayOf(Manifest.permission.RECORD_AUDIO)
            ActivityCompat.requestPermissions(this, array, requestPermissionCode)
        } else if (audioOk && !cameraOk) {
    
    
            val array = arrayOf(Manifest.permission.CAMERA)
            ActivityCompat.requestPermissions(this, array, requestPermissionCode)
        }
    }

    @SuppressLint("MissingPermission")
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    
    
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == requestPermissionCode) {
    
    
            for (i in grantResults) {
    
    
                if (i != PackageManager.PERMISSION_GRANTED) {
    
    
                    Toast.makeText(this, "请开启相机和录音权限", Toast.LENGTH_SHORT).show()
                    finish()
                    return
                }
            }
            openCamera()
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    
    
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == requestActivityCode) {
    
    
            when (resultCode) {
    
    
                //确认
                Activity.RESULT_OK -> {
    
    
                    val fileName = mFileName ?: return
                    val filePath = mFilePath ?: return
                    //不能再录制
                    mRecordable = false
                    val intent = Intent()
                    //把录制的视频文件名、文件路径传给外面调用的页面
                    intent.putExtra(FILE_NAME, fileName)
                    intent.putExtra(FILE_PATH, filePath)
                    setResult(Activity.RESULT_OK, intent)
                    finish()
                }
                //重新录制
                Activity.RESULT_CANCELED -> {
    
    
                    //删除文件,重新录制
                    deleteRecordFile()
                }
            }
        }
    }

    /**
     * 页面暂停时,关闭相机,停止录制
     */
    override fun onStop() {
    
    
        super.onStop()
        close()
        mOrientationEventListener?.disable()
        mRecordThreadHandler.removeMessages(mStartRecordMsg)
    }

    override fun onDestroy() {
    
    
        super.onDestroy()
        mRecordThreadHandler.looper.quit()
        mPreviewSurface?.release()
        mRecordSurface?.release()
        mMediaRecorder?.release()
        mMediaRecorder = null
    }

    /**
     * 准备录制相关处理
     */
    @RequiresPermission(Manifest.permission.CAMERA)
    fun openCamera() {
    
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    
    
            //使用context可能会出现内存泄漏(红米手机上),CameraManager会一直持有context
            val cameraManager = applicationContext.getSystemService(CAMERA_SERVICE) as? CameraManager
            val cameraIdList = cameraManager?.cameraIdList
            if (cameraManager == null || cameraIdList == null || cameraIdList.isEmpty()) {
    
    
                Toast.makeText(this, "无法使用设备相机", Toast.LENGTH_SHORT).show()
                finish()
                return
            }
            for (id in cameraIdList) {
    
    
                var cameraCharacteristics: CameraCharacteristics? = null
                try {
    
    
                    cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
                } catch (t: Throwable) {
    
    
                    t.printStackTrace()
                }
                if (cameraCharacteristics == null) {
    
    
                    continue
                }
                val fensFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
                if (fensFacing != mFensFacing) {
    
    
                    continue
                }
                val level = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
                val capabilities = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)  
                mCameraOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
                val map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
//                //获取预览支持的分辨率
//                val outputSizes = map.getOutputSizes(SurfaceHolder::class.java)
//                Log.i("TAG", "prepareRecord: outputSizes ${Arrays.toString(outputSizes)}")
                //获取录制支持的分辨率
                val recorderSizes = map.getOutputSizes(MediaRecorder::class.java)
//                Log.i("TAG", "prepareRecord: recorderSizes ${Arrays.toString(recorderSizes)}")
                val recordSize = getRecordSize(recorderSizes)
                mRecordSize = recordSize
                resizeSurfaceSize(recordSize.width, recordSize.height)
                mSurfaceView.holder.setFixedSize(recordSize.width, recordSize.height)
                try {
    
    
                    cameraManager.openCamera(id, mCameraDeviceStateCallback, mRecordThreadHandler)
                } catch (t: Throwable) {
    
    
                    t.printStackTrace()
                    Toast.makeText(this, "相机打开失败,请关闭重试", Toast.LENGTH_SHORT).show()
                }
                break
            }
        } else {
    
    
            //6.0以下
            Toast.makeText(this, "Android系统版本太低不支持", Toast.LENGTH_SHORT).show()
            finish()
        }
    }

    private val mCameraDeviceStateCallback: CameraDevice.StateCallback by lazy(LazyThreadSafetyMode.NONE) {
    
    
        object : CameraDevice.StateCallback() {
    
    
            override fun onOpened(camera: CameraDevice) {
    
    
                mCameraDevice = camera
                val recordSize = mRecordSize ?: return
                //预览surface
                val previewSurface = mSurfaceView.holder.surface
                mPreviewSurface = previewSurface
                setupMediaRecorder(recordSize.width, recordSize.height, false)
                val recordSurface = mRecordSurface
                mRecordSurface = recordSurface
                val surfaceList = listOf(previewSurface, recordSurface)
                camera.createCaptureSession(surfaceList, mCameraCaptureSessionStateCallback, mRecordThreadHandler)
            }

            override fun onDisconnected(camera: CameraDevice) {
    
    
                //相机连接断开
                if (mCameraDevice != null) {
    
    
                    close()
                } else {
    
    
                    camera.close()
                }
            }

            override fun onError(camera: CameraDevice, error: Int) {
    
    
                camera.close()
                mUiHandler.sendEmptyMessage(mCameraOpenFailMsg)
            }
        }
    }

    private val mCameraCaptureSessionStateCallback: CameraCaptureSession.StateCallback by lazy {
    
    
        object : CameraCaptureSession.StateCallback(){
    
    
            override fun onConfigured(session: CameraCaptureSession) {
    
    
                mCameraCaptureSession = session
                val camera = mCameraDevice ?: return
                val previewSurface = mPreviewSurface ?: return
                val recordSurface = mRecordSurface ?: return
                val captureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
                mCaptureRequest = captureRequest
                captureRequest.addTarget(previewSurface)
                captureRequest.addTarget(recordSurface)
                
                //对焦
                captureRequest.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
                //自动曝光
                captureRequest.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON)
                //进行重复请求录制预览
                session.setRepeatingRequest(captureRequest.build(), mCameraCaptureSessionCaptureCallback, mRecordThreadHandler)
            }

            override fun onConfigureFailed(session: CameraCaptureSession) {
    
    
                session.close()
                mUiHandler.sendEmptyMessage(mCameraPreviewFailMsg)
            }
        }
    }

    private val mCameraCaptureSessionCaptureCallback: CameraCaptureSession.CaptureCallback by lazy(LazyThreadSafetyMode.NONE){
    
    
        object :CameraCaptureSession.CaptureCallback(){
    
    
            override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
    
    
                super.onCaptureCompleted(session, request, result)
                //这个方法在预览过长中,会一直被回调
//                Log.i("TAG", "onCaptureCompleted thread ${Thread.currentThread().name}")
            }

            override fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failure: CaptureFailure) {
    
    
                super.onCaptureFailed(session, request, failure)
            }
        }
    }

    /**
     * 录制线程的Handler
     */
    private val mRecordThreadHandler: Handler by lazy(LazyThreadSafetyMode.NONE) {
    
    
        val recordThread = HandlerThread("RecordVideoThread")
        recordThread.start()
        object : Handler(recordThread.looper) {
    
    
            override fun handleMessage(msg: Message) {
    
    
                if (isFinishing || isDestroyed) return
                when (msg.what) {
    
    
                    mStartRecordMsg -> {
    
    //开始录制
                        if (!mRecordingState) {
    
    //不是开始录制状态,return
                            return
                        }
                        val recordSize = mRecordSize ?: return
                        try {
    
    
                            //重新配置MediaRecorder,因为用户刚打开页面时的手机方向,和点击录制时的手机方向可能不一样,所以重新配置。注意是为了支持横屏录制的视频为横屏视频,不然都是竖屏视频
                            setupMediaRecorder(recordSize.width, recordSize.height, true)
                            //视图变为录制状态
                            mMediaRecorder?.start()
                        } catch (t: Throwable) {
    
    
                            t.printStackTrace()
                            //录制出现错误
                            mUiHandler.sendEmptyMessage(mRecordErrorMsg)
                        }
                    }
                }
            }
        }
    }

    /**
     * ui线程Handler 处理录制倒计时,相机打开失败相关消息
     */
    private val mUiHandler: Handler by lazy(LazyThreadSafetyMode.NONE) {
    
    
        object : Handler(Looper.getMainLooper()) {
    
    
            override fun handleMessage(msg: Message?) {
    
    
                if (isFinishing || isDestroyed) {
    
    
                    return
                }
                when(msg?.what){
    
    
                    mCountDownMsg -> {
    
    
                        mCurrentRecordDuration += 1
                        val progress = (mCurrentRecordDuration * 1f / mMaxRecordDuration * 100 + 0.5f).toInt()
                        mRecordProgressBar.progress = progress
                        if (mCurrentRecordDuration >= mMaxRecordDuration) {
    
    
                            //录制时间到了,停止录制
                            stopRecord()
                            mRecordComplete = true
                            //跳转预览页面
                            openPlayActivity()
                        } else {
    
    
                            sendEmptyMessageDelayed(mCountDownMsg, 1000)
                        }
                    }
                    mCameraOpenFailMsg -> {
    
    
                        Toast.makeText(this@VideoRecord23Activity, "相机打开失败,请关闭重试", Toast.LENGTH_SHORT).show()
                    }
                    mCameraPreviewFailMsg -> {
    
    
                        Toast.makeText(this@VideoRecord23Activity, "相机预览失败,请关闭重试", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }

    /**
     * 创建并配置 MediaRecorder
     * @param width 视频宽度
     * @param height 视频高度
     * @param  outputFileCreated 输出文件是否已经创建;第一次prepare时,文件已经创建了,开始录制时,不用再次创建文件
     */
    private fun setupMediaRecorder(width: Int, height: Int, outputFileCreated: Boolean): MediaRecorder {
    
    
        var mediaRecorder = mMediaRecorder
        if (mediaRecorder == null) {
    
    
            mediaRecorder = MediaRecorder()
            mMediaRecorder = mediaRecorder
        } else {
    
    
            mediaRecorder.reset()
        }
        var recordSurface = mRecordSurface
        if (recordSurface == null) {
    
    
            recordSurface = MediaCodec.createPersistentInputSurface()
            mRecordSurface = recordSurface
        }
        mediaRecorder.setInputSurface(recordSurface)
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
        //数据源来之surface
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)

        //设置QUALITY_HIGH,可以提高视频的录制质量(文件也会变大),但是不能设置编码格式和帧率等参数,否则报错
        val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P)
        mediaRecorder.setProfile(profile)
        mediaRecorder.setMaxFileSize(0)
        mediaRecorder.setVideoSize(width, height)
        //视频方向
        mediaRecorder.setOrientationHint(mRecordVideoOrientation)
        //录制文件没有创建,创建文件
        if (!outputFileCreated) {
    
    
            val parentFile = externalCacheDir ?: cacheDir
            val fileName = "${
      
      System.currentTimeMillis()}.mp4"
            mFileName = fileName
            mFilePath = parentFile.absolutePath + File.separator + fileName
        }
        //不设置setOutputFile prepare时会报错
        mediaRecorder.setOutputFile(mFilePath)

        //prepare之后recordSurface才能用
        mediaRecorder.prepare()
        return mediaRecorder
    }

    /**
     * 页面关闭或不在前台时,停止录制、释放相机
     */
    private fun close() {
    
    
        if (mRecordingState) {
    
    
            //停止录制
            stopRecord()
        }
        if (!mRecordComplete) {
    
    
            //没有录制完成,或者没有开始录制过(MediaRecorder prepare时会创建文件),删除录制的文件
            deleteRecordFile()
        }
        //释放相机
        val previewSurface = mPreviewSurface
        if (previewSurface != null) {
    
    
            mCaptureRequest?.removeTarget(previewSurface)
        }
        val recordSurface = mRecordSurface
        if (recordSurface != null) {
    
    
            mCaptureRequest?.removeTarget(recordSurface)
        }
        mCameraCaptureSession?.close()
        mCameraDevice?.close()
        mCaptureRequest = null
        mCameraCaptureSession = null
        mCameraDevice = null
    }

    /**
     * 删除录制的文件
     */
    private fun deleteRecordFile() {
    
    
        val filePath = mFilePath ?: return
        try {
    
    
            val file = File(filePath)
            if (file.exists()) {
    
    
                file.delete()
            }
            mFilePath = null
        } catch (t: Throwable) {
    
    
            t.printStackTrace()
        }
    }

    /**
     * 获取录制的视频尺寸
     * @param sizes 支持的尺寸列表
     */
    private fun getRecordSize(sizes: Array<Size>): Size {
    
    
        //参考尺寸 1280*720
        val compareWidth = 1280
        val compareHeight = 720
        var resultSize = sizes[0]
        var minDiffW = Int.MAX_VALUE
        var minDiffH = Int.MAX_VALUE
        for (size in sizes) {
    
    
            if (size.width == compareWidth && size.height == compareHeight) {
    
    
                resultSize = size
                break
            }
            //找到最接近 1280*720的size
            val diffW = abs(size.width - compareWidth)
            val diffH = abs(size.height - compareHeight)
            if (diffW < minDiffW && diffH < minDiffH) {
    
    
                minDiffW = diffW
                minDiffH = diffH
                resultSize = size
            }
        }
        return resultSize
    }

    /**
     * 根据视频宽高,修改surfaceView的宽高,来适应预览尺寸
     *
     * @param width  预览宽度
     * @param height 预览高度
     */
    private fun resizeSurfaceSize(height: Int, width: Int) {
    
    
        val displayW: Int = mSurfaceView.width
        val displayH: Int = mSurfaceView.height
        if (displayW == 0 || displayH == 0) return
        var ratioW = 1f
        var ratioH = 1f
        if (width != displayW) {
    
    
            ratioW = width * 1f / displayW
        }
        if (height != displayH) {
    
    
            ratioH = height * 1f / displayH
        }
        var finalH = displayH
        var finalW = displayW
        if (ratioW >= ratioH) {
    
    
            finalH = (height / ratioW).toInt()
        } else {
    
    
            finalW = (width / ratioH).toInt()
        }
        val layoutParams = mSurfaceView.layoutParams
        if (layoutParams.width == finalW && layoutParams.height == finalH) {
    
    
            return
        }
        layoutParams.width = finalW
        layoutParams.height = finalH
        mSurfaceView.layoutParams = layoutParams
    }

    /**
     * 监听手机方向改变,计算录制时的视频方向。横屏录制时,视频横屏。竖屏录制时,视频竖屏
     */
    private fun initOrientationEventListener() {
    
    
        val orientationEventListener = object : OrientationEventListener(this) {
    
    
            override fun onOrientationChanged(orientation: Int) {
    
    
                if (orientation == ORIENTATION_UNKNOWN) return
                val rotation = (orientation + 45) / 90 * 90
                if (mFensFacing == CameraCharacteristics.LENS_FACING_BACK) {
    
    
                    //后置摄像头
                    mRecordVideoOrientation = (mCameraOrientation + rotation) % 360
                } else if (mFensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
    
    
                    //前置摄像头
                    mRecordVideoOrientation = mCameraOrientation - rotation
                }
            }
        }
        mOrientationEventListener = orientationEventListener
    }

    /**
     * 跳转录制视频预览页面
     */
    private fun openPlayActivity() {
    
    
        //val intent = Intent(this, VideoPlayActivity::class.java)
        //intent.putExtra(VideoPlayActivity.FILE_PATH, mFilePath)
        //startActivity(intent)
    }
}

猜你喜欢

转载自blog.csdn.net/ganduwei/article/details/129778795