基于Camera2和MediaRecorder实现视频录制

本文介绍了在Android中使用Camera2和MediaRecorder实现视频录制的详细过程,包括如何启动系统录制、自定义录制功能、处理横屏录制和视频方向问题。代码示例展示了如何创建和配置CameraCaptureSession、MediaRecorder,以及处理预览和录制Surface。文章特别指出,对于Android6.0以下版本,由于使用了特定API,因此不支持该实现方式。
摘要由CSDN通过智能技术生成

一、概述

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

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

  //跳转系统的录制视频页面
  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)
    }
}
### 回答1: 安卓camera2拍照录制视频demo是基于安卓相机API的一个示例应用程序,用于演示如何使用安卓相机2 API来实现拍照和录制视频功能。 在此示例应用程序中,首先需要初始化相机设备并获取相机特性、参数等信息。然后,可以通过创建一个用于预览的SurfaceView或TextureView,并将其设置为相机的预览输出目标。 在拍照功能方面,可以通过设置拍照的一些参数,如图片格式、拍照模式、闪光灯模式等。然后,可以通过调用相机的拍照方法,触发相机拍照操作,并在拍照完成后保存图片。 在录制视频功能方面,需要创建一个用于录制视频MediaRecorder对象,并设置视频的输出格式、编码格式、视频大小等参数。然后,可以通过设置相机的预览输出目标为MediaRecorder的Surface,并调用MediaRecorder的start方法开始录制视频,调用stop方法停止录制。 此外,在实际开发中,还需要处理相机权限的获取和申请,以及相机的生命周期管理,如在Activity的onResume和onPause方法中初始化和释放相机等。还可以根据需要添加其他功能,如自动对焦、曝光调节等。 总之,安卓camera2拍照录制视频demo提供了一个基本的框架和实现思路,供开发者参考和借鉴,在此基础上可以根据实际需求进行扩展和定制。 ### 回答2: 安卓Camera2是用于在安卓设备上进行相机操作的API。它提供了更多的功能和控制选项,可以实现更高级的相机操作,比如手动对焦、曝光控制、帧率控制等。 要编写一个拍照录制视频的Demo,首先需要获取相机的权限。在AndroidManifest.xml文件中添加相机权限的声明: <uses-permission android:name="android.permission.CAMERA" /> 然后,在布局文件中添加一个相机预览的SurfaceView: <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> 接下来,在Activity中获取相机实例并进行相关配置。首先创建一个CameraManager对象,通过它来获取相机ID和打开相机: CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); String cameraId = cameraManager.getCameraIdList()[0]; cameraManager.openCamera(cameraId, cameraStateCallback, null); 在相机状态回调中,可以配置相机预览的Surface和启动预览: CameraCaptureSession.StateCallback sessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { // 设置相机预览的Surface CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); requestBuilder.addTarget(surfaceView.getHolder().getSurface()); // 启动预览 session.setRepeatingRequest(requestBuilder.build(), null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { // 配置失败处理 } }; 最后,编写点击拍照和录制视频的逻辑。点击拍照时,创建一个ImageReader对象,设置监听器来处理拍照结果: ImageReader imageReader = ImageReader.newInstance(imageWidth, imageHeight, ImageFormat.JPEG, 1); imageReader.setOnImageAvailableListener(onImageAvailableListener, null); ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { // 处理拍照结果 } }; 点击开始录制视频时,创建一个MediaRecorder对象,并配置输出文件、音频源、视频源等参数。然后使用CameraCaptureSession进行录制会话: MediaRecorder mediaRecorder = new MediaRecorder(); mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); // 配置输出文件,视频尺寸等参数 sessionStateCallback.onConfigured(cameraCaptureSession); // 启动预览 mediaRecorder.prepare(); mediaRecorder.start(); 以上是一个简单的安卓Camera2拍照录制视频Demo的实现。通过Camera2 API可以实现更多的相机功能和控制选项,可以根据实际需求进行更多的定制和扩展。 ### 回答3: 安卓的Camera2 API是一个强大的相机框架,可以实现高效的拍照和录制视频功能。 首先,我们需要在AndroidManifest.xml文件中声明相机和音频录制权限。例如: <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> 然后,我们需要在Activity或Fragment中创建一个CameraManager对象来管理相机设备。例如: private CameraManager mCameraManager; mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); 接下来,我们需要获取可用的相机列表,并选择一个需要使用的相机设备。例如: String cameraId = null; try { for (String id : mCameraManager.getCameraIdList()) { CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id); int facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing == CameraCharacteristics.LENS_FACING_BACK) { cameraId = id; break; } } } catch (CameraAccessException e) { e.printStackTrace(); } 然后,我们可以打开相机设备并开始预览。例如: try { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { mCameraManager.openCamera(cameraId, mCameraStateCallback, null); } } catch (CameraAccessException e) { e.printStackTrace(); } 在相机预览过程中,我们可以设置一些相机参数,例如预览尺寸、拍照尺寸、闪光灯模式等。例如: CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); captureBuilder.addTarget(mPreviewSurface); captureBuilder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); 然后,我们可以通过调用CameraCaptureSession的capture方法来拍照。例如: mCameraCaptureSession.capture(captureBuilder.build(), mCaptureCallback, null); 如果要录制视频,可以通过MediaRecorder实现。例如: private void startRecording() throws IOException { mMediaRecorder = new MediaRecorder(); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setOutputFile(mOutputFile.getAbsolutePath()); // 设置视频参数 mMediaRecorder.setVideoEncodingBitRate(10000000); mMediaRecorder.setVideoFrameRate(30); mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); // 设置音频参数 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setAudioChannels(1); mMediaRecorder.setAudioSamplingRate(16000); mMediaRecorder.setAudioEncodingBitRate(32000); mMediaRecorder.prepare(); mMediaRecorder.start(); } 最后,我们可以在相机的onClosed方法中释放相机资源和停止录制。例如: @Override public void onClosed(CameraDevice camera) { mCameraDevice.close(); mCameraDevice = null; if (mMediaRecorder != null) { mMediaRecorder.stop(); mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; } } 通过上述步骤,我们可以实现一个简单的安卓Camera2拍照和录制视频的demo。希望对你有所帮助!
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值