cameraX视频录制 拷贝直接用

Posted 六道对穿肠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了cameraX视频录制 拷贝直接用相关的知识,希望对你有一定的参考价值。

效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3i0EaImv-1637722081187)(https://liudao01.github.io/picture/img/视频录制.gif)]

https://liudao01.github.io/picture/img/视频录制.gif

activity代码

package com.sinochem.www.station.activity

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.pm.PackageManager
import android.graphics.Point
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.SystemClock
import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.View
import android.widget.*
import androidx.annotation.NonNull
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
import com.sinochem.www.station.R
import com.sinochem.www.station.base.BaseActivity
import com.sinochem.www.station.utils.*
import com.sinochem.www.station.utils.camerax.CameraXCustomPreviewView
import com.sinochem.www.station.utils.camerax.FocusImageView
import com.sinochem.www.station.view.LoadingFragment
import java.io.File
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

// Typealias (本文下称 "类型别名")。类型别名可以使您在不增加新类型的情况下,为现有类或函数类型提供替代名称。
typealias LumaListener = (luma: Double) -> Unit

class CameraXActivity : BaseActivity(), View.OnClickListener 
    private val title: RelativeLayout by lazy  findViewById<RelativeLayout>(R.id.title) 
    private val viewFinder: CameraXCustomPreviewView by lazy 
        findViewById<CameraXCustomPreviewView>(
            R.id.viewFinder
        )
    
    private val back: ImageView by lazy  findViewById<ImageView>(R.id.back) 

    private val switchBtn: Button by lazy  findViewById<Button>(R.id.switch_btn) 
    private val chronometer: Chronometer by lazy  findViewById<Chronometer>(R.id.chronometer) 
    private val recorderStart: ImageView by lazy  findViewById<ImageView>(R.id.recorder_start) 
    private val recorderStop: ImageView by lazy  findViewById<ImageView>(R.id.recorder_stop) 
    private val focusView: FocusImageView by lazy  findViewById<FocusImageView>(R.id.focusView) 


    private lateinit var cameraExecutor: ExecutorService

    var cameraProvider: ProcessCameraProvider? = null//相机信息
    var preview: Preview? = null//预览对象
    var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//当前相机
    var camera: Camera? = null//相机对象
    private var imageCapture: ImageCapture? = null//拍照用例
    var videoCapture: VideoCapture? = null//录像用例

    var maxMinuie = 180 //最大时间

    val minMinuie = 5 //最小时间
    private var progressDialog: ProgressDialog? = null

    var localPath: String? = null;

    var isTimeOver: Boolean = false //是否时间结束

    var alertDialogBuilder: AlertDialog.Builder? = null

    override fun setLayoutId(): Int 
        return R.layout.activity_camerax_record;
    

    override fun initVariables() 
        if (intent != null) 
            maxMinuie = intent.getIntExtra("maxMinuie", 180)
        
        LogUtil.d("最大录制时长 :  $maxMinuie")

        // 设置视频文件输出的路径
        // 设置视频文件输出的路径
        localPath = (PathUtil.getInstance().videoPath
                + System.currentTimeMillis() + ".mp4")
    

    override fun initViews(savedInstanceState: Bundle?) 
    

    override fun doBusiness() 
        recorderStop.setOnClickListener(this)
        recorderStart.setOnClickListener(this)
        back.setOnClickListener(this)

        chronometer.onChronometerTickListener =
            Chronometer.OnChronometerTickListener  chronometer ->
                val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
                val contentDescription = chronometer.contentDescription
                val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
                LogUtil.d("打印回调计时器 = " + second)
                if (maxMinuie <= second) 
                    isTimeOver = true
                    recorderStopAction();
                
            
    


    @SuppressLint("ClickableViewAccessibility")
    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
//        setContentView()

        if (allPermissionsGranted()) 
            startCamera()
         else 
            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        


//        camera_capture_button.setOnClickListener  takePhoto() 
//        recorderStart.setOnClickListener 
            btnStartVideo.text = "Stop Video"
//            takeVideo()
//        
        switchBtn.setOnClickListener 
            if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) 
                cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
             else 
                cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            
            startCamera()
        
    

    /**
     * 初始化手势动作
     */
    private fun initCameraListener() 
        val zoomState = camera!!.cameraInfo.zoomState
        viewFinder.setCustomTouchListener(object : CameraXCustomPreviewView.CustomTouchListener 
            override fun zoom(delta: Float) 
                //双指缩放
//                zoomState.value?.let 
//                    val currentZoomRatio = it.zoomRatio
//                    camera!!.cameraControl.setZoomRatio(currentZoomRatio * delta)
//                
            

            override fun click(x: Float, y: Float) 
                //点击对焦
                if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) 
                    val factory = viewFinder.createMeteringPointFactory(cameraSelector)
                    val point = factory.createPoint(x, y)
                    val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                        .setAutoCancelDuration(3, TimeUnit.SECONDS)
                        .build()
                    focusView.startFocus(Point(x.toInt(), y.toInt()))
                    val future: ListenableFuture<*> =
                        camera!!.cameraControl.startFocusAndMetering(action)
                    future.addListener(Runnable 
                        try 
                            val result = future.get() as FocusMeteringResult
                            if (result.isFocusSuccessful) 
                                focusView.onFocusSuccess()
                             else 
                                focusView.onFocusFailed()
                            
                         catch (e: Exception) 
                            Log.e("", "", e)
                        
                    , cameraExecutor)
                
            

            override fun doubleClick(x: Float, y: Float) 
                //双击放大缩小
//                zoomState.value?.let 
//                    val currentZoomRatio = it.zoomRatio
//                    if (currentZoomRatio > it.minZoomRatio) 
//                        camera!!.cameraControl.setLinearZoom(0f)
//                     else 
//                        camera!!.cameraControl.setLinearZoom(0.5f)
//                    
//                
            

            override fun longClick(x: Float, y: Float) 
        )
    


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) 
        if (requestCode == REQUEST_CODE_PERMISSIONS) 
            if (allPermissionsGranted()) 
                startCamera()
             else 
                Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT)
                    .show()
//                finish()
                sendVideo()
            
        
    


    @SuppressLint("RestrictedApi")
    private fun startCamera() 
//        if (cameraExecutor != null) 
        cameraExecutor = Executors.newSingleThreadExecutor()
//        
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener(Runnable 
            cameraProvider = cameraProviderFuture.get()//获取相机信息

            //预览配置
            preview = Preview.Builder()
                .build()
                .also 
                    it.setSurfaceProvider(viewFinder.createSurfaceProvider())
                

            imageCapture = ImageCapture.Builder().build()//拍照用例配置

            val imageAnalyzer = ImageAnalysis.Builder()
                .build()
                .also 
                    it.setAnalyzer(cameraExecutor, LuminosityAnalyzer  luma ->
                        Log.d(TAG, "Average luminosity: $luma")
                    )
                

            //比特率
            videoCapture = VideoCapture.Builder()//录像用例配置
//                .setBitRate(3 * 1024 * 1024)较为清晰,且文件大小
                .setBitRate(900 * 1024)较为清晰,且文件大小为3.26M(30秒)
                .setVideoFrameRate(20)//帧率 视频帧率  越高视频体积越大
//                .setAudioBitRate(1024)//设置音频的码率
                .setTargetResolution(Size(720, 1080))//setTargetResolution设置生成的视频的分辨率
//                .setTargetResolution(Size(720,1080))//setTargetResolution设置生成的视频的分辨率
//                .setTargetAspectRatio(AspectRatio.RATIO_16_9) //设置高宽比
//                .setTargetRotation(viewFinder.display.rotation)//设置旋转角度
//                .setAudioRecordSource(Audiosource.MIC)//设置音频源麦克风
                .build()

            try 
                cameraProvider?.unbindAll()//先解绑所有用例
                camera = cameraProvider?.bindToLifecycle(
                    this,
                    cameraSelector,
                    preview,
                    imageCapture,
                    videoCapture
                )//绑定用例
             catch (exc: Exception) 
                Log.e(TAG, "Use case binding failed", exc)
            

            initCameraListener()
        , ContextCompat.getMainExecutor(this))
    

    //拍照
    private fun takePhoto() 
        val imageCapture = imageCapture ?: return

        val file = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path +
                    "/CameraX" + SimpleDateFormat(
                FILENAME_FORMAT,
                Locale.CHINA
            ).format(System.currentTimeMillis()) + ".jpg"
        )

        val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()

        imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
            object : ImageCapture.OnImageSavedCallback 
                override fun onError(exc: ImageCaptureException) 
                    Log.e(TAG, "Photo capture failed: $exc.message", exc)
                

                override fun onImageSaved(output: ImageCapture.OutputFileResults) 
                    val savedUri = Uri.fromFile(file)
                    val msg = "Photo capture succeeded: $savedUri"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                
            )
    


    //录制视频
    @SuppressLint("RestrictedApi", "ClickableViewAccessibility")
    private fun takeVideo() 
        recorderStop.visibility = View.VISIBLE
        recorderStart.visibility = View.GONE
        //视频保存路径
        val file = File(localPath)
        //开始录像
        videoCapture?.startRecording(
            file,
            Executors.newSingleThreadExecutor(),
            object : VideoCapture.OnVideoSavedCallback 
                override fun onVideoSaved(@NonNull file: File) 
                    //保存视频成功回调,会在停止录制时被调用
                    LogUtil.d("onVideoSaved: file.absolutePath = " + file.absolutePath)
//                    finish()
//                    recorderStopAction(false)
//                    showSelectDialog(isTimeOver)
                    runOnUiThread 
                        LoadingFragment.dismiss()
                        showSelectDialog(isTimeOver)
                    
//                  Toast.makeText(this@MainActivity, file.absolutePath, Toast.LENGTH_SHORT).show()
                

                override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) 
                    //保存失败的回调,可能在开始或结束录制时被调用
                    Log.e("", "onError: $message")
                    runOnUiThread 
                        LoadingFragment.dismiss()
                        ToastUtils.showCenter("视频处理失败")
                        finish()
                    
                
            )


    

    @SuppressLint("RestrictedApi")
    private fun stopRecording() 
        videoCapture?.stopRecording()//停止录制
        preview?.clear()//清除预览
        recorderStop.visibility = View.GONE
        recorderStart.visibility = View.VISIBLE
        chronometer.stop()
        chronometer.base = SystemClock.elapsedRealtime()
    


    private fun recorderStopAction() 
        LogUtil.d("视频停止看看是否重复调用")
//        videoCapture.

//        recorderStop.setEnabled(false)
        // 停止拍摄
        var weakReference: WeakReference<Activity> = WeakReference(this)
        LoadingFragment.showLodingDialog(weakReference.get())
        stopRecording()


        //                btn_switch.setVisibility(View.VISIBLE);
    

    private fun showSelectDialog(isTime: Boolean) 

        var msg = "是否发送视频"
        if (isTime) 
            msg = "录制时间已到" + maxMinuie / 60 + "分钟,是否发送视频"
        
        if (alertDialogBuilder == null) 
            alertDialogBuilder = AlertDialog.Builder(this)
                .setMessage(msg)
                .setPositiveButton("确定")  dialog, which ->
                    dialog.dismiss()
                    sendVideo()
                
                .setNegativeButton("取消")  dialog, which ->
                    if (localPath != null) 
                        val file: File = File(localPath)
                        if (file.exists()) file.delete()
                    
                    finish()
                .setCancelable(false)
        
        alertDialogBuilder?.show()

    

    fun sendVideo() 
        if (TextUtils.isEmpty(localPath)) 
            LogUtil.e( "recorder fail please try again!")
            return
        
        setResult(Activity.RESULT_OK, intent.putExtra("uri", localPath))
        finish()
    

    private fun stopOrSave() 

        val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
        val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
        LogUtil.d("second $second")
        try 
            if (second < minMinuie) 
                ToastUtils.showCenter("视频录制时间最少5秒")
                return
            
            recorderStop.isEnabled = false
            recorderStart.isEnabled = false
            recorderStopAction()
         catch (e: Exception) 
            ToastUtils.showCenter("操作异常,请返回后重试")
            e.printStackTrace()
            CrashReportUtil.getInstance().postException(e)
        
    

    override fun onPause() 
        super.onPause()
        if (progressDialog != null && progressDialog?.isShowing == true) 
            progressDialog?.dismiss()
        

    

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all 
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
    

    @SuppressLint("RestrictedApi")
    override fun onDestroy() 
        super.onDestroy()
        alertDialogBuilder = null
        cameraExecutor.shutdown()
    

    private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer 

        private fun ByteBuffer.toByteArray(): ByteArray 
            rewind()
            val data = ByteArray(remaining())
            get(data)
            return data
        

        override fun analyze(image: ImageProxy) 

            val buffer = image.planes[0].buffer
            val data = buffer.toByteArray()
            val pixels = data.map  it.toInt() and 0xFF 
            val luma = pixels.average()

            listener(luma)

            image.close()
        
    

    companion object 
        private const val TAG = "CameraXBasic"
        private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = arrayOf(
            Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO
        )
    

    override fun onBackPressed() 
//        super.onBackPressed()
        stopOrSave()
    

    override fun onClick(v: View?) 
        when (v?.id) 
            R.id.recorder_start -> 
                // 重置其他
                chronometer.base = SystemClock.elapsedRealtime()
                chronometer.start()
                takeVideo()
            
            R.id.back,
            R.id.recorder_stop -> 
                isTimeOver = false;
                stopOrSave()
            

        
    

项目地址.

https://gitee.com/liudao/camera-xvideo/tree/master

以上是关于cameraX视频录制 拷贝直接用的主要内容,如果未能解决你的问题,请参考以下文章

cameraX视频录制 拷贝直接用

Android 使用CameraX实现预览/拍照/录制视频/图片分析/对焦/缩放/切换摄像头等操作

kotlin 使用CameraX录制视频点击对焦,保存至相册中

Android CameraX - 录制视频时的面部检测

Android CameraX实现摄像头预览拍照录制视频

具有自定义表面的 Android CameraX