基于Camera2和MediaRecorder实现视频录制

这篇具有很好参考价值的文章主要介绍了基于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(),主要是为了能支持横屏录制的视频方向为横屏。

先看看演示效果:
mediarecorder镜像,音视频,android,数码相机

二、实现方案和细节

使用的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还是同一个。文章来源地址https://www.toymoban.com/news/detail-516232.html

		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)
    }
}

到了这里,关于基于Camera2和MediaRecorder实现视频录制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 安卓MediaRecorder(4)视频采集编码写入详细源码分析

    本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134896821 最新更新地址 https://gitee.com/chenjim/chenjimblog 通过 文2 我们知道了 MediaRecorder 各个接口 Framework 中的实现。 通过 文3 我们 知道了 MediaRecorder 底层音频的采集、编码、写入文件等详细流程。 本文主要介绍 MediaRecorder 视频

    2024年01月25日
    浏览(22)
  • Android中使用原生MediaRecorder APi实现录音功能

    一、MediaRecorder简介 MediaRecorder是Android中的一个API,可以用来实现录音功能。它继承自android.media.MediaRecorder类,可以实现音频和视频的录制。 二、MediaRecorder的使用 1、首先,实例化一个MediaRecorder对象,并设置音频源: 2、设置音频的输出格式: 3、设置音频的编码格式: 4、设

    2024年02月09日
    浏览(32)
  • MediaRecorder API的使用

    MediaRecorder API是一个Web API,用于在浏览器中录制音频和视频。以下是使用MediaRecorder API的基本步骤: 1.获取媒体输入设备:首先,你需要获取用户的媒体输入设备(摄像头和/或麦克风)的访问权限。这可以通过调用 navigator.mediaDevices.getUserMedia() 方法来完成。 2.创建MediaRecorde

    2024年02月06日
    浏览(27)
  • NDK Android平台camera2采集视频

    《Android平台使用camera2采集视频 代码实现 》链接: https://edu.csdn.net/learn/38258/606148?spm=1003.2001.3001.4157 在Android平台开发实时音视频项目,摄像头的采集是一个必不可少的流程;通常在Android平台上采集摄像头数据可以使用Camera1接口、Camera2接口或者CameraX接口。Camera1接口只支持

    2024年02月11日
    浏览(54)
  • 安卓MediaRecorder(3)音频采集编码写入详细源码分析

    本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134896808 最新更新地址 https://gitee.com/chenjim/chenjimblog 通过 文2,我们知道 MediaRecorder 相关接口是在 StagefrightRecorder.cpp 中实现,本文进一步分析音频采集、编码、写入文件详细流程。 音频初始化 通过前文,我们知道 setupAudioEn

    2024年01月17日
    浏览(24)
  • Camera 录制视频 掉帧、退帧

    录制出来的视频帧率有问题 30帧录制出来的不够,出现丢帧、 退帧的问题 1. 录制出来的掉帧 、预览不容易看出 ( 预览看不出来,因为预览绘制实际是 来什么我绘制什么,但是编码器不一样 ) 1.1 视频模式,录制出来的视频掉帧 1.2 vsync通路方式录制视频不掉帧 1.3 GL录制有

    2023年04月11日
    浏览(21)
  • 十分钟实现 Android Camera2 相机拍照

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 上篇文章 我们使用 Camera2 实现了相机预览的功能,这篇文章我们接着上文,来实现 Cam

    2024年02月11日
    浏览(40)
  • 十分钟实现 Android Camera2 相机预览

    因为工作中要使用 Android Camera2 API ,但因为 Camera2 比较复杂,网上资料也比较乱,有一定入门门槛,所以花了几天时间系统研究了下,并在 CSDN 上记录了下,希望能帮助到更多的小伙伴。 Camera2 API 的包名是 android.hardware.camera2 ,是 Android 5.0 后推出的一套调用摄像头设备的接口

    2024年02月13日
    浏览(49)
  • Android studio Camera2实现的详细流程

    前提 TextureView.SurfaceTextureListener是一个接口,用于监听TextureView中的SurfaceTexture的状态更改。在使用相机时,您可以使用TextureView来显示相机预览。通过实现SurfaceTextureListener接口,您可以在SurfaceTexture准备好时开始相机预览,并在SurfaceTexture销毁时停止预览。 注意 : 必须是在

    2024年02月05日
    浏览(33)
  • 原生Camera2的对焦原理和框架,以及代码实现流程

    在Android中,Camera2 API提供了对相机硬件的底层访问,包括对焦功能。以下是Camera2对焦原理和框架的简要概述,以及代码实现流程: 对焦原理和框架: 预览: 在开始对焦之前,通常需要先启动相机的预览。预览不仅允许用户看到实时视频流,还可以提供关于相机状态的信息,

    2024年01月16日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包