欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

自定义音视频基类一

程序员文章站 2022-07-01 13:53:38
...

安卓音视频开发相关的知识实在是太多了,如果每个都按之前那样写会很烦琐,为了偷懒决定写一个音视频的基类解决这个问题,本章内容为最基础的预览模块,废话不多说先上基类,后面为使用示例

abstract class BaseVideoActivity() : BaseActivity() {

    private lateinit var mBackgroundThread: HandlerThread
    private var mBackgroundHandler: Handler? = null

    //摄像头管理类
    lateinit var cameraManager: CameraManager

    //摄像头id列表
    lateinit var cameraIdList: Array<String>

    //第几个摄像头
    var index = 0

    //当前摄像头id
    lateinit var cameraId: String

    //当前摄像头
    private lateinit var cameraDevice: CameraDevice

    //Surface集合
    private lateinit var outputs:List<Surface>

    //Session
    private lateinit var cameraCaptureSession: CameraCaptureSession

    /**
     * 初始化Camera2
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun initCamera2() {
        cameraManager = application.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        cameraIdList = cameraManager.cameraIdList
        cameraId = cameraIdList[index]
    }

    /**
     * 获取CameraCharacteristics相机属性类
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun getCameraCharacteristics(): CameraCharacteristics {
        return cameraManager.getCameraCharacteristics(cameraId)
    }

    /**
     * 设置预设的预览尺寸
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun setPreviewSize(@NotNull surfaceTexture: SurfaceTexture, cameraCharacteristics: CameraCharacteristics): Size {
        val aspectRatios = ArrayList<Float>()
        aspectRatios.add(16.toFloat() / 9)
        aspectRatios.add(4.toFloat() / 3)
        aspectRatios.add(18.toFloat() / 9)

        val size = getPreviewSize(cameraCharacteristics, aspectRatios)
        surfaceTexture.setDefaultBufferSize(size.width, size.height)

        return size
    }

    /**
     * 获取预览尺寸
     * 参数2:预览尺寸比例的集合,按加入顺序寻找预览尺寸并返回
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun getPreviewSize(@NotNull cameraCharacteristics: CameraCharacteristics, aspectRatios: ArrayList<Float>): Size {
        for (aspectRatio in aspectRatios) {
            val size = getPreviewSize(cameraCharacteristics, aspectRatio)
            if (size != null) {
                return size
            }
        }
        return Size(1280, 720)
    }

    /**
     * 获取预览尺寸
     * 参数2:预览尺寸比例,如4:3,16:9
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun getPreviewSize(@NotNull cameraCharacteristics: CameraCharacteristics, aspectRatio: Float): Size? {
        val streamConfigurationMap =
                cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
        val supportedSizes = streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)
        for (size in supportedSizes) {
            if (size.width.toFloat() / size.height == aspectRatio) {
                return size
            }
        }
        return null
    }

    /**
     * 打开摄像头
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun openCamera() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            cameraManager.openCamera(cameraId, callback, getBackgroundHandler())
        } else {
            val dialog = AlertDialog.Builder(this)
            dialog.setTitle("开启相机失败").setMessage("缺少开启相机的权限").setCancelable(false)

            dialog.setNegativeButton("取消") { _, _ ->

            }

            dialog.setPositiveButton("授权") { _, _ ->
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data = Uri.parse("package:$packageName")
                startActivity(intent)
            }

            dialog.show()
        }
    }

    /**
     * 打开摄像头的回调
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private val callback = object : CameraDevice.StateCallback() {
        //成功打开时的回调,可以得到一个 CameraDevice 实例
        override fun onOpened(camera: CameraDevice) {
            cameraDevice = camera
            outputs = createCaptureSession(cameraDevice)
        }

        //当 camera 不再可用时的回调,通常在该方法中进行资源释放的操作
        override fun onDisconnected(camera: CameraDevice) {
            showToast("camera不再可用")
        }

        // 当 camera 打开失败时的回调,error 为具体错误原因,通常在该方法中也要进行资源释放的操作
        override fun onError(camera: CameraDevice, error: Int) {
            camera.close()
            showError(error)
            releaseBackgroundThread()
        }

        //相机关闭时回调
        override fun onClosed(camera: CameraDevice) {
            super.onClosed(camera)
            cameraCaptureSession.close()
        }
    }

    /**
     * 创建一个Session
     */
    abstract fun createCaptureSession(camera: CameraDevice):List<Surface>

    /**
     * 创建预览Session的回调
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    val mSessionCallback = object : CameraCaptureSession.StateCallback() {

        override fun onConfigured(session: CameraCaptureSession) {
            cameraCaptureSession = session
            setRepeatingRequest(cameraCaptureSession)
        }

        //创建失败
        override fun onConfigureFailed(session: CameraCaptureSession) {
            showToast("创建Session失败")
        }
    }

    /**
     * 开始预览,即设置反复请求
     */
    abstract fun setRepeatingRequest(session: CameraCaptureSession)

    /**
     * Session进度的回调
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    val captureCallback = object : CameraCaptureSession.CaptureCallback() {
        override fun onCaptureCompleted(
                session: CameraCaptureSession,
                request: CaptureRequest,
                result: TotalCaptureResult
        ) {
            super.onCaptureCompleted(session, request, result)
        }

        override fun onCaptureFailed(
                session: CameraCaptureSession,
                request: CaptureRequest,
                failure: CaptureFailure
        ) {
            super.onCaptureFailed(session, request, failure)
            when (failure.reason) {
                CaptureFailure.REASON_ERROR -> {
                    showToast("Capture failed: REASON_ERROR")
                    cameraCaptureSession.close()
                }
                CaptureFailure.REASON_FLUSHED -> showToast("Capture failed: REASON_FLUSHED")
                else -> showToast("Capture failed: UNKNOWN")
            }
        }
    }

    /**
     * 切换摄像头
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun switchCamera(){
        if (cameraDevice != null){
            if (index < cameraIdList.size - 1) {
                index++
            } else {
                index = 0
            }
            cameraId = cameraIdList[index]
            cameraDevice?.close()
            openCamera()
        }else{
            showToast("请先开启摄像头")
        }
    }

    /**
     * 获取BackgroundHandler
     */
    fun getBackgroundHandler(): Handler {
        if (mBackgroundHandler == null) {
            //设置摄像头线程
            mBackgroundThread = HandlerThread("CameraBackground")
            mBackgroundThread.start()
            mBackgroundHandler = Handler(mBackgroundThread.looper)
        }
        return mBackgroundHandler as Handler
    }

    /**
     * 释放线程资源
     */
    fun releaseBackgroundThread() {
        mBackgroundHandler?.removeCallbacksAndMessages(null)
        mBackgroundHandler = null
        mBackgroundThread.quitSafely()
        mBackgroundThread.join()
    }

    /**
     * 开启摄像头错误提示
     */
    fun showError(error: Int) {
        when (error) {
            CameraDevice.StateCallback.ERROR_CAMERA_IN_USE -> {
                showToast("当前相机设备已经在一个更高优先级的地方打开了")
            }
            CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE -> {
                showToast("已打开相机数量到上限了,无法再打开新的相机了")
            }
            CameraDevice.StateCallback.ERROR_CAMERA_DISABLED -> {
                showToast("由于相关设备策略该相机设备无法打开")
            }
            CameraDevice.StateCallback.ERROR_CAMERA_DEVICE -> {
                showToast("相机设备发生了一个致命错误")
            }
            CameraDevice.StateCallback.ERROR_CAMERA_SERVICE -> {
                showToast("相机服务发生了一个致命错误")
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            cameraDevice.close()
            for (surface in outputs){
                surface.release()
            }
            releaseBackgroundThread()
        }
    }

}

使用示例

class LiveBroadcastActivity : BaseVideoActivity() {

    //预览Surface
    private lateinit var surface: Surface

    //相机预览分辨率
    private lateinit var size: Size

    //预览CaptureRequest.Builder
    private lateinit var previewCaptureRequest: CaptureRequest.Builder

    override fun getLayoutId(): Int {
        return R.layout.activity_live_broadcast;
    }

    override fun init() {
        //开启surfaceTextureListener监听,获取Surface
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //初始化Camera2
            initCamera2()
            //开启监听
            textureView.surfaceTextureListener = surfaceTextureListener

            switchCamera.setOnClickListener {
                switchCamera()
            }
        }
    }

    /**
     * 获取Surface的回调
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
        //SurfaceTexture大小发生变化时调用
        override fun onSurfaceTextureSizeChanged(
                surfaceTexture: SurfaceTexture,
                width: Int,
                height: Int
        ) {
            //获取相机属性类
            val cameraCharacteristics = getCameraCharacteristics()
            //设置预览尺寸
            size = setPreviewSize(surfaceTexture, cameraCharacteristics)

            surface = Surface(surfaceTexture)
        }

        override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {

        }

        override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture?): Boolean {
            return true
        }

        override fun onSurfaceTextureAvailable(
                surfaceTexture: SurfaceTexture,
                width: Int,
                height: Int
        ) {
            //获取相机属性类
            val cameraCharacteristics = getCameraCharacteristics()
            //设置预览尺寸
            size = setPreviewSize(surfaceTexture, cameraCharacteristics)

            surface = Surface(surfaceTexture)
            //开启摄像头
            openCamera()
        }
    }

    /**
     * 创建一个Session
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun createCaptureSession(camera: CameraDevice):List<Surface>{
        //创建一个预览的CaptureRequest
        previewCaptureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        // 设置预览输出的 Surface
        previewCaptureRequest.addTarget(surface)

        val outputs = ArrayList<Surface>()
        outputs.add(surface)

        //创建一个Session
        camera.createCaptureSession(
                outputs,
                mSessionCallback,
                getBackgroundHandler()
        )

        return outputs
    }

    /**
     * 开始预览,即设置反复请求
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    override fun setRepeatingRequest(session: CameraCaptureSession) {
        session.setRepeatingRequest(
                previewCaptureRequest.build(),
                captureCallback,
                getBackgroundHandler()
        )
    }

    /**
     * 自动修改textureView宽高以适应不同预览比例
     */
    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            val width = textureView.width
            val height = textureView.height
            val proportion1 = size.width.toFloat() / size.height
            val proportion2 = height.toFloat() / width
            if (proportion1 > proportion2) {
                val layoutParams = textureView.layoutParams
                layoutParams.width = (height * proportion1 + .5).toInt()
                textureView.layoutParams = layoutParams
            } else if (proportion1 < proportion2) {
                val layoutParams = textureView.layoutParams
                layoutParams.height = (width * proportion1 + .5).toInt()
                textureView.layoutParams = layoutParams
            }
        }
    }
}