国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

基于Camera2和MediaRecorder實(shí)現(xiàn)視頻錄制

這篇具有很好參考價(jià)值的文章主要介紹了基于Camera2和MediaRecorder實(shí)現(xiàn)視頻錄制。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

一、概述

視頻錄制,在一般開(kāi)發(fā)中很少遇到,大部分開(kāi)發(fā)工作都是寫(xiě)寫(xiě)頁(yè)面,請(qǐng)求接口,展示數(shù)據(jù)等等。真要遇到,可能采用第三方庫(kù)實(shí)現(xiàn),一來(lái)實(shí)現(xiàn)快速,二來(lái)可能覺(jué)得別人實(shí)現(xiàn)的比較好。特別是在開(kāi)發(fā)周期很緊的情況下,一般都不會(huì)自己花時(shí)間實(shí)現(xiàn)。

其實(shí)最好是使用手機(jī)系統(tǒng)的錄制視頻,功能完善,穩(wěn)定。實(shí)現(xiàn)起來(lái)最簡(jiǎn)單,簡(jiǎn)簡(jiǎn)單單幾句代碼:

  //跳轉(zhuǎn)系統(tǒng)的錄制視頻頁(yè)面
  val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
  intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY,1)
  intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,30)//錄制時(shí)長(zhǎng)
  startActivityForResult(intent, 666)

   //打開(kāi)手機(jī)的選擇視頻頁(yè)
//  val intent = Intent()
//  intent.action = Intent.ACTION_PICK
//  intent.data = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
//  startActivityForResult(intent,666)

然后在onActiityResult方法中接收錄制好的視頻路徑處理即可。

但如果需求是像微信那樣app內(nèi)的錄制視頻,就不能使用系統(tǒng)自帶錄制功能,需要自己實(shí)現(xiàn)。

下面將我自己實(shí)現(xiàn)的,記錄下來(lái)。這里也只是實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的錄制功能,甚至還有問(wèn)題:前置攝像頭錄制視頻是鏡像的。
另外下面的實(shí)現(xiàn)不支持在Android6.0以下的手機(jī)上使用,因?yàn)槭褂玫搅薃PI23的方法:MediaCodec.createPersistentInputSurface(),主要是為了能支持橫屏錄制的視頻方向?yàn)闄M屏。

先看看演示效果:
mediarecorder鏡像,音視頻,android,數(shù)碼相機(jī)

二、實(shí)現(xiàn)方案和細(xì)節(jié)

使用的Camera2 和 MediaRecorder。
如果使用Camera1的話,可能會(huì)更簡(jiǎn)單一些,Camera2用起來(lái)確實(shí)相對(duì)麻煩一點(diǎn)。不過(guò)Camera1畢竟已經(jīng)被棄用了,且使用Camera1打開(kāi)相機(jī)比Camera2要耗時(shí)一些。

Camera2使用

  1. 用CameraManager獲取相機(jī)Id列表cameraIdList,然后openCamera指定的相機(jī)id,打開(kāi)相機(jī)
  2. 打開(kāi)成功后,使用 CameraDevice.createCaptureSession 創(chuàng)建CameraCaptureSession
  3. 創(chuàng)建成功后,使用CameraCaptureSession.setRepeatingRequest 發(fā)起預(yù)覽請(qǐng)求,它需要傳入CaptureRequest,通過(guò)CameraDevice.captureRequest創(chuàng)建,CaptureRequest可以設(shè)置一些參數(shù),對(duì)焦、曝光、閃光燈等等

第2步 createCaptureSession 時(shí)需要傳入Surface列表。

這里傳入了兩個(gè)Surface,一個(gè)是預(yù)覽使用,由SurfaceView提供。
另一個(gè)是錄制使用,通過(guò)MediaCodec.createPersistentInputSurface() 創(chuàng)建,設(shè)置給MediaRecorder。
如果預(yù)覽時(shí)不創(chuàng)建MediaRecorder,只傳入預(yù)覽Surface,等到點(diǎn)擊錄制時(shí),才創(chuàng)建MediaRecorder,需要重新創(chuàng)建createCaptureSession,傳入新的Surface,這樣雖然可以,但是點(diǎn)擊錄制時(shí)會(huì)很慢,預(yù)覽畫(huà)面會(huì)斷一下。

第2步傳入的Surface列表,還需要在第3步中使用CaptureRequest.addTarget 添加,兩個(gè)地方必須對(duì)應(yīng),不然發(fā)起預(yù)覽請(qǐng)求時(shí)會(huì)報(bào)錯(cuò)。

MediaRecorder配置

因?yàn)槭褂玫氖荂amera2,所以不能使用MediaRecorder.setCamera()。
替代方法是使用MediaRecorder.surface,前提是需要設(shè)置數(shù)據(jù)源為Surface

mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)

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

后面因?yàn)橐С謾M屏錄制,沒(méi)有采用 mediaRecorder.surface 。
如果進(jìn)入頁(yè)面開(kāi)啟相機(jī)預(yù)覽時(shí)手機(jī)豎屏,點(diǎn)擊錄制時(shí)手機(jī)橫屏,因?yàn)樵陬A(yù)覽時(shí)就創(chuàng)建了mediaRecorder,并且setOrientationHint確定了視頻方向,無(wú)法再改變(只能prepare之前設(shè)置),這時(shí)錄制的視頻方向肯定就不對(duì)。

要改變視頻方向,只能重新創(chuàng)建 mediaRecorder ,但是重新創(chuàng)建mediaRecorder,同時(shí)也重新創(chuàng)建了一個(gè)新的Sueface,需要重新createCaptureSession傳入新的Sueface。(改成點(diǎn)擊錄制時(shí),創(chuàng)建mediaRecorder,然后重新createCaptureSession,測(cè)試中也發(fā)現(xiàn)畫(huà)面會(huì)斷一下,效果不好)。

正因如此,最終改為使用 MediaCodec.createPersistentInputSurface() 創(chuàng)建 Surface,然后 setInputSurface 給 mediaRecorder。MediaCodec.createPersistentInputSurface()創(chuàng)建的Surface只有在mediaRecorder.prepare之后才可用。 在點(diǎn)擊錄制時(shí),重新配置mediaRecorder,設(shè)置新的方向。這樣雖然mediaRecorder重新配置了,但是Surface還是同一個(gè)。文章來(lái)源地址http://www.zghlxwxcb.cn/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)
        //數(shù)據(jù)源來(lái)之surface
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
        //設(shè)置QUALITY_HIGH,可以提高視頻的錄制質(zhì)量(文件也會(huì)變大),但是不能設(shè)置編碼格式和幀率等參數(shù),否則報(bào)錯(cuò)
        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"
        //不設(shè)置setOutputFile prepare時(shí)會(huì)報(bào)錯(cuò)
        mediaRecorder.setOutputFile(parentFile.absolutePath + File.separator + fileName)
        //prepare之后recordSurface才能用
        mediaRecorder.prepare()

三、全部代碼

/**
 * 錄制視頻
 * 支持后置、前置攝像頭切換(但前置攝像頭錄制視頻是鏡像的,需要翻轉(zhuǎn)) 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 錄制進(jìn)度倒計(jì)時(shí)msg,一秒鐘發(fā)送一次
     * mStartRecordMsg 開(kāi)始錄制
     * mCameraOpenFailMsg 相機(jī)打開(kāi)失敗
     * mCameraPreviewFailMsg 相機(jī)預(yù)覽失敗
     * mRecordErrorMsg 錄制出現(xiàn)錯(cuò)誤
     * 在 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

    /**
     * 預(yù)覽畫(huà)面尺寸,和視頻錄制尺寸
     */
    @Volatile
    private var mRecordSize: Size? = null

    /**
     * 相機(jī)方向
     */
    private var mCameraOrientation: Int = 0

    /**
     * 錄制視頻的方向,隨著手機(jī)方向的改變而改變
     */
    @Volatile
    private var mRecordVideoOrientation: Int = 0

    /**
     * 默認(rèn)打開(kāi)后置相機(jī) LENS_FACING_BACK
     * 可以切換為前置相機(jī) LENS_FACING_FRONT
     */
    private var mFensFacing = CameraCharacteristics.LENS_FACING_BACK

    /**
     * 預(yù)覽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
    /**
     * 是否是錄制中的狀態(tài)
     * true:錄制中
     */
    @Volatile
    private var mRecordingState = false

    /**
     * 是否錄制完成。從手動(dòng)點(diǎn)擊開(kāi)始錄制到手動(dòng)點(diǎn)擊停止錄制(或者錄制時(shí)長(zhǎng)倒計(jì)時(shí)到了),為錄制完成,值為true。其他情況為false
     */
    private var mRecordComplete = false

    /**
     * 閃光燈狀態(tài)
     * true 開(kāi)啟
     * false 關(guān)閉
     */
    private var mFlashlightState = false

    /**
     * 是否可以錄制
     * 錄制完成,跳轉(zhuǎn)播放頁(yè)面后,返回時(shí)點(diǎn)擊的完成,不能錄制
     * 其他情況都為可以錄制
     */
    private var mRecordable = true

    /**
     * 錄制最大時(shí)長(zhǎng),時(shí)間到了之后錄制完成
     * 單位:秒
     */
    private var mMaxRecordDuration = 30

    /**
     * 已錄制的時(shí)長(zhǎng)
     */
    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
                    //跳轉(zhuǎn)預(yù)覽頁(yè)面
                    openPlayActivity()
                } else {
                    startRecord()
                    mOrientationEventListener?.disable()
                }
            }
            R.id.flashlightIv -> {
                val captureRequest = mCaptureRequest ?: return
                val cameraCaptureSession = mCameraCaptureSession ?: return
                if (mFlashlightState) {
                    //閃光燈開(kāi)啟,點(diǎn)擊關(guān)閉
                    captureRequest[CaptureRequest.FLASH_MODE] = CaptureRequest.FLASH_MODE_OFF
                    mFlashlightIv.setImageResource(R.drawable.flashlight_off)
                } else {
                    //閃關(guān)燈關(guān)閉,點(diǎn)擊開(kāi)啟
                    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) {
                    //當(dāng)前打開(kāi)的是后置攝像頭,切換到前置攝像頭
                    mFensFacing = CameraCharacteristics.LENS_FACING_FRONT
                    close()
                    openCamera()
                } else if (mFensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
                    //當(dāng)前打開(kāi)的是前置攝像頭,切換到后置攝像頭
                    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
        //開(kāi)始錄制倒計(jì)時(shí)
        mUiHandler.sendEmptyMessageDelayed(mCountDownMsg, 1000)
    }

    private fun stopRecord() {
        //視圖變?yōu)?停止錄制狀態(tài)
        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()
        }
    }

    /**
     * 檢查相機(jī)和錄音與權(quán)限,并打開(kāi)相機(jī)
     */
    private fun checkPermissionAndOpenCamera(){
        //錄制音頻權(quán)限ok
        val audioOk = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
        //相機(jī)權(quán)限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, "請(qǐng)開(kāi)啟相機(jī)和錄音權(quán)限", 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) {
                //確認(rèn)
                Activity.RESULT_OK -> {
                    val fileName = mFileName ?: return
                    val filePath = mFilePath ?: return
                    //不能再錄制
                    mRecordable = false
                    val intent = Intent()
                    //把錄制的視頻文件名、文件路徑傳給外面調(diào)用的頁(yè)面
                    intent.putExtra(FILE_NAME, fileName)
                    intent.putExtra(FILE_PATH, filePath)
                    setResult(Activity.RESULT_OK, intent)
                    finish()
                }
                //重新錄制
                Activity.RESULT_CANCELED -> {
                    //刪除文件,重新錄制
                    deleteRecordFile()
                }
            }
        }
    }

    /**
     * 頁(yè)面暫停時(shí),關(guān)閉相機(jī),停止錄制
     */
    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
    }

    /**
     * 準(zhǔn)備錄制相關(guān)處理
     */
    @RequiresPermission(Manifest.permission.CAMERA)
    fun openCamera() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //使用context可能會(huì)出現(xiàn)內(nèi)存泄漏(紅米手機(jī)上),CameraManager會(huì)一直持有context
            val cameraManager = applicationContext.getSystemService(CAMERA_SERVICE) as? CameraManager
            val cameraIdList = cameraManager?.cameraIdList
            if (cameraManager == null || cameraIdList == null || cameraIdList.isEmpty()) {
                Toast.makeText(this, "無(wú)法使用設(shè)備相機(jī)", 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
//                //獲取預(yù)覽支持的分辨率
//                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, "相機(jī)打開(kāi)失敗,請(qǐng)關(guān)閉重試", Toast.LENGTH_SHORT).show()
                }
                break
            }
        } else {
            //6.0以下
            Toast.makeText(this, "Android系統(tǒng)版本太低不支持", 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
                //預(yù)覽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) {
                //相機(jī)連接斷開(kāi)
                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)
                
                //對(duì)焦
                captureRequest.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
                //自動(dòng)曝光
                captureRequest.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON)
                //進(jìn)行重復(fù)請(qǐng)求錄制預(yù)覽
                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)
                //這個(gè)方法在預(yù)覽過(guò)長(zhǎng)中,會(huì)一直被回調(diào)
//                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 -> {//開(kāi)始錄制
                        if (!mRecordingState) {//不是開(kāi)始錄制狀態(tài),return
                            return
                        }
                        val recordSize = mRecordSize ?: return
                        try {
                            //重新配置MediaRecorder,因?yàn)橛脩?hù)剛打開(kāi)頁(yè)面時(shí)的手機(jī)方向,和點(diǎn)擊錄制時(shí)的手機(jī)方向可能不一樣,所以重新配置。注意是為了支持橫屏錄制的視頻為橫屏視頻,不然都是豎屏視頻
                            setupMediaRecorder(recordSize.width, recordSize.height, true)
                            //視圖變?yōu)殇浿茽顟B(tài)
                            mMediaRecorder?.start()
                        } catch (t: Throwable) {
                            t.printStackTrace()
                            //錄制出現(xiàn)錯(cuò)誤
                            mUiHandler.sendEmptyMessage(mRecordErrorMsg)
                        }
                    }
                }
            }
        }
    }

    /**
     * ui線程Handler 處理錄制倒計(jì)時(shí),相機(jī)打開(kāi)失敗相關(guān)消息
     */
    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) {
                            //錄制時(shí)間到了,停止錄制
                            stopRecord()
                            mRecordComplete = true
                            //跳轉(zhuǎn)預(yù)覽頁(yè)面
                            openPlayActivity()
                        } else {
                            sendEmptyMessageDelayed(mCountDownMsg, 1000)
                        }
                    }
                    mCameraOpenFailMsg -> {
                        Toast.makeText(this@VideoRecord23Activity, "相機(jī)打開(kāi)失敗,請(qǐng)關(guān)閉重試", Toast.LENGTH_SHORT).show()
                    }
                    mCameraPreviewFailMsg -> {
                        Toast.makeText(this@VideoRecord23Activity, "相機(jī)預(yù)覽失敗,請(qǐng)關(guān)閉重試", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }

    /**
     * 創(chuàng)建并配置 MediaRecorder
     * @param width 視頻寬度
     * @param height 視頻高度
     * @param  outputFileCreated 輸出文件是否已經(jīng)創(chuàng)建;第一次prepare時(shí),文件已經(jīng)創(chuàng)建了,開(kāi)始錄制時(shí),不用再次創(chuàng)建文件
     */
    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)
        //數(shù)據(jù)源來(lái)之surface
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)

        //設(shè)置QUALITY_HIGH,可以提高視頻的錄制質(zhì)量(文件也會(huì)變大),但是不能設(shè)置編碼格式和幀率等參數(shù),否則報(bào)錯(cuò)
        val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P)
        mediaRecorder.setProfile(profile)
        mediaRecorder.setMaxFileSize(0)
        mediaRecorder.setVideoSize(width, height)
        //視頻方向
        mediaRecorder.setOrientationHint(mRecordVideoOrientation)
        //錄制文件沒(méi)有創(chuàng)建,創(chuàng)建文件
        if (!outputFileCreated) {
            val parentFile = externalCacheDir ?: cacheDir
            val fileName = "${System.currentTimeMillis()}.mp4"
            mFileName = fileName
            mFilePath = parentFile.absolutePath + File.separator + fileName
        }
        //不設(shè)置setOutputFile prepare時(shí)會(huì)報(bào)錯(cuò)
        mediaRecorder.setOutputFile(mFilePath)

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

    /**
     * 頁(yè)面關(guān)閉或不在前臺(tái)時(shí),停止錄制、釋放相機(jī)
     */
    private fun close() {
        if (mRecordingState) {
            //停止錄制
            stopRecord()
        }
        if (!mRecordComplete) {
            //沒(méi)有錄制完成,或者沒(méi)有開(kāi)始錄制過(guò)(MediaRecorder prepare時(shí)會(huì)創(chuàng)建文件),刪除錄制的文件
            deleteRecordFile()
        }
        //釋放相機(jī)
        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
    }

    /**
     * 根據(jù)視頻寬高,修改surfaceView的寬高,來(lái)適應(yīng)預(yù)覽尺寸
     *
     * @param width  預(yù)覽寬度
     * @param height 預(yù)覽高度
     */
    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
    }

    /**
     * 監(jiān)聽(tīng)手機(jī)方向改變,計(jì)算錄制時(shí)的視頻方向。橫屏錄制時(shí),視頻橫屏。豎屏錄制時(shí),視頻豎屏
     */
    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
    }

    /**
     * 跳轉(zhuǎn)錄制視頻預(yù)覽頁(yè)面
     */
    private fun openPlayActivity() {
        //val intent = Intent(this, VideoPlayActivity::class.java)
        //intent.putExtra(VideoPlayActivity.FILE_PATH, mFilePath)
        //startActivity(intent)
    }
}

到了這里,關(guān)于基于Camera2和MediaRecorder實(shí)現(xiàn)視頻錄制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 安卓MediaRecorder(4)視頻采集編碼寫(xiě)入詳細(xì)源碼分析

    本文首發(fā)地址 https://blog.csdn.net/CSqingchen/article/details/134896821 最新更新地址 https://gitee.com/chenjim/chenjimblog 通過(guò) 文2 我們知道了 MediaRecorder 各個(gè)接口 Framework 中的實(shí)現(xiàn)。 通過(guò) 文3 我們 知道了 MediaRecorder 底層音頻的采集、編碼、寫(xiě)入文件等詳細(xì)流程。 本文主要介紹 MediaRecorder 視頻

    2024年01月25日
    瀏覽(17)
  • Android中使用原生MediaRecorder APi實(shí)現(xiàn)錄音功能

    一、MediaRecorder簡(jiǎn)介 MediaRecorder是Android中的一個(gè)API,可以用來(lái)實(shí)現(xiàn)錄音功能。它繼承自android.media.MediaRecorder類(lèi),可以實(shí)現(xiàn)音頻和視頻的錄制。 二、MediaRecorder的使用 1、首先,實(shí)例化一個(gè)MediaRecorder對(duì)象,并設(shè)置音頻源: 2、設(shè)置音頻的輸出格式: 3、設(shè)置音頻的編碼格式: 4、設(shè)

    2024年02月09日
    瀏覽(19)
  • MediaRecorder API的使用

    MediaRecorder API是一個(gè)Web API,用于在瀏覽器中錄制音頻和視頻。以下是使用MediaRecorder API的基本步驟: 1.獲取媒體輸入設(shè)備:首先,你需要獲取用戶(hù)的媒體輸入設(shè)備(攝像頭和/或麥克風(fēng))的訪問(wèn)權(quán)限。這可以通過(guò)調(diào)用 navigator.mediaDevices.getUserMedia() 方法來(lái)完成。 2.創(chuàng)建MediaRecorde

    2024年02月06日
    瀏覽(20)
  • NDK Android平臺(tái)camera2采集視頻

    NDK Android平臺(tái)camera2采集視頻

    《Android平臺(tái)使用camera2采集視頻 代碼實(shí)現(xiàn) 》鏈接: https://edu.csdn.net/learn/38258/606148?spm=1003.2001.3001.4157 在Android平臺(tái)開(kāi)發(fā)實(shí)時(shí)音視頻項(xiàng)目,攝像頭的采集是一個(gè)必不可少的流程;通常在Android平臺(tái)上采集攝像頭數(shù)據(jù)可以使用Camera1接口、Camera2接口或者CameraX接口。Camera1接口只支持

    2024年02月11日
    瀏覽(24)
  • 安卓MediaRecorder(3)音頻采集編碼寫(xiě)入詳細(xì)源碼分析

    本文首發(fā)地址 https://blog.csdn.net/CSqingchen/article/details/134896808 最新更新地址 https://gitee.com/chenjim/chenjimblog 通過(guò) 文2,我們知道 MediaRecorder 相關(guān)接口是在 StagefrightRecorder.cpp 中實(shí)現(xiàn),本文進(jìn)一步分析音頻采集、編碼、寫(xiě)入文件詳細(xì)流程。 音頻初始化 通過(guò)前文,我們知道 setupAudioEn

    2024年01月17日
    瀏覽(17)
  • Camera 錄制視頻 掉幀、退幀

    Camera 錄制視頻 掉幀、退幀

    錄制出來(lái)的視頻幀率有問(wèn)題 30幀錄制出來(lái)的不夠,出現(xiàn)丟幀、 退幀的問(wèn)題 1. 錄制出來(lái)的掉幀 、預(yù)覽不容易看出 ( 預(yù)覽看不出來(lái),因?yàn)轭A(yù)覽繪制實(shí)際是 來(lái)什么我繪制什么,但是編碼器不一樣 ) 1.1 視頻模式,錄制出來(lái)的視頻掉幀 1.2 vsync通路方式錄制視頻不掉幀 1.3 GL錄制有

    2023年04月11日
    瀏覽(25)
  • 十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)拍照

    十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)拍照

    因?yàn)楣ぷ髦幸褂?Android Camera2 API ,但因?yàn)?Camera2 比較復(fù)雜,網(wǎng)上資料也比較亂,有一定入門(mén)門(mén)檻,所以花了幾天時(shí)間系統(tǒng)研究了下,并在 CSDN 上記錄了下,希望能幫助到更多的小伙伴。 上篇文章 我們使用 Camera2 實(shí)現(xiàn)了相機(jī)預(yù)覽的功能,這篇文章我們接著上文,來(lái)實(shí)現(xiàn) Cam

    2024年02月11日
    瀏覽(94)
  • 十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)預(yù)覽

    十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)預(yù)覽

    因?yàn)楣ぷ髦幸褂?Android Camera2 API ,但因?yàn)?Camera2 比較復(fù)雜,網(wǎng)上資料也比較亂,有一定入門(mén)門(mén)檻,所以花了幾天時(shí)間系統(tǒng)研究了下,并在 CSDN 上記錄了下,希望能幫助到更多的小伙伴。 Camera2 API 的包名是 android.hardware.camera2 ,是 Android 5.0 后推出的一套調(diào)用攝像頭設(shè)備的接口

    2024年02月13日
    瀏覽(42)
  • Android studio Camera2實(shí)現(xiàn)的詳細(xì)流程

    前提 TextureView.SurfaceTextureListener是一個(gè)接口,用于監(jiān)聽(tīng)TextureView中的SurfaceTexture的狀態(tài)更改。在使用相機(jī)時(shí),您可以使用TextureView來(lái)顯示相機(jī)預(yù)覽。通過(guò)實(shí)現(xiàn)SurfaceTextureListener接口,您可以在SurfaceTexture準(zhǔn)備好時(shí)開(kāi)始相機(jī)預(yù)覽,并在SurfaceTexture銷(xiāo)毀時(shí)停止預(yù)覽。 注意 : 必須是在

    2024年02月05日
    瀏覽(21)
  • 原生Camera2的對(duì)焦原理和框架,以及代碼實(shí)現(xiàn)流程

    原生Camera2的對(duì)焦原理和框架,以及代碼實(shí)現(xiàn)流程

    在Android中,Camera2 API提供了對(duì)相機(jī)硬件的底層訪問(wèn),包括對(duì)焦功能。以下是Camera2對(duì)焦原理和框架的簡(jiǎn)要概述,以及代碼實(shí)現(xiàn)流程: 對(duì)焦原理和框架: 預(yù)覽: 在開(kāi)始對(duì)焦之前,通常需要先啟動(dòng)相機(jī)的預(yù)覽。預(yù)覽不僅允許用戶(hù)看到實(shí)時(shí)視頻流,還可以提供關(guān)于相機(jī)狀態(tài)的信息,

    2024年01月16日
    瀏覽(24)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包