Android Camera库打包

YIN 发布于 2025-10-25 65 次阅读


简介

为了快速使用Android设备的相机 打包了camera2库
该库只需要很少的配置即可获取相机的部分重要参数,并启用其中一个选中的相机

用法

  1. 使用init(applicationContext)获取上下文
  2. 使用getCamerasList()获取相机参数
  3. 使用setFrameCallback()设置回调
  4. 使用setCameraParameters()选择目标相机
  5. 使用closeCamera()关闭相机

附件

源码:

import android.Manifest
import android.content.Context
import android.graphics.ImageFormat
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCaptureSession
import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraDevice
import android.hardware.camera2.CameraManager
import android.hardware.camera2.CaptureRequest
import android.media.Image
import android.media.ImageReader
import android.media.MediaRecorder
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import android.util.Range
import android.util.Size
import androidx.annotation.RequiresPermission

// 分辨率与帧率信息类
data class ResInfo(
    val width: Int,
    val height: Int,
    val fpsRanges: List<Range<Int>> // 帧率范围列表(如[30,30]或[60,120])
)

data class CameraInfo_t (
    var id: Int = 0,
    var focalLength: Float = 0.0f,
    var equalsFocalLength: Float = 0.0f,
    var resolutions: List<ResInfo> = emptyList()
)

object CameraController {

    private lateinit var applicationContext: Context

    fun init(applicationContext: Context) {
        this.applicationContext = applicationContext.applicationContext
    }

    private var CameraInfos: MutableList<CameraInfo_t> = mutableListOf()
    private lateinit var cameraManager: CameraManager
    private lateinit var cameraDevice: CameraDevice
    private lateinit var imageReader: ImageReader

    private lateinit var backgroundThread: HandlerThread
    private lateinit var backgroundHandler: Handler

    private lateinit var captureSession: CameraCaptureSession

    private var frameCallback: FrameCallback? = null

    private var fpsRange: Range<Int>? = null


    fun interface FrameCallback {
        fun onFrameAvailable(imageData: Image, width: Int, height: Int)
    }

    fun setFrameCallback(callback: FrameCallback) {
        this.frameCallback = callback
    }

    // 启动后台线程
    private fun startBackgroundThread() {
        Log.d("startBackgroundThread","Active")
        backgroundThread = HandlerThread("CameraBackground")
        backgroundThread.start()
        backgroundHandler = Handler(backgroundThread.looper)
    }

    // 停止后台线程
    private fun stopBackgroundThread() {
        backgroundThread.quitSafely()
        try {
            backgroundThread.join()
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
    }

    // 获取camera信息列表
    public fun getCamerasList():List<CameraInfo_t> {
        cameraManager = applicationContext.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        try {
            val cameraIds = cameraManager.cameraIdList
            for (cameraId in cameraIds) {
                var cameraInfo = CameraInfo_t()
                Log.d("ID",cameraId)
                cameraInfo.id = cameraId.toInt()
                val characteristics = cameraManager.getCameraCharacteristics(cameraId)

                // 1. 获取所有支持的分辨率及帧率范围
                val streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
                streamMap?.let { map ->
                    val videoSizes = map.getOutputSizes(MediaRecorder::class.java)
                    cameraInfo.resolutions = videoSizes?.map { size ->
                        val fpsRanges = if (map.highSpeedVideoSizes.contains(size)) {
                            // 仅高帧率分辨率可调用此方法
                            map.getHighSpeedVideoFpsRangesFor(size)?.toList() ?: emptyList()
                        } else {
                            // 普通帧率:获取标准帧率范围
                            map.getOutputMinFrameDuration(MediaRecorder::class.java, size)?.let {
                                val fps = (1_000_000_000.0 / it).toInt()
                                listOf(Range(fps, fps)) // 构造固定帧率范围
                            } ?: emptyList()
                        }
                        ResInfo(size.width, size.height, fpsRanges)
                    } ?: emptyList()
                }

                cameraInfo.resolutions.forEach { i ->
                    Log.d("CameraInfo", "分辨率: ${i.width} * ${i.height} 帧率: ${i.fpsRanges} " )
                }


                val focalLengths = characteristics.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)
                val sensorSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)

                val diagonal35mm = 43.27f
                val sensorDiagonal = kotlin.math.hypot(sensorSize!!.width, sensorSize.height)

                focalLengths?.let { lengths ->
                    if(lengths.isNotEmpty()) {
                        val mainFocalLength = lengths[0]
                        val equivalentFocalLength = mainFocalLength * (diagonal35mm / sensorDiagonal)
                        Log.d("CameraInfo", "相机焦距: $mainFocalLength mm, 等效焦距: $equivalentFocalLength mm")
                        cameraInfo.focalLength = mainFocalLength
                        cameraInfo.equalsFocalLength = equivalentFocalLength
                    }
                }
                CameraInfos.add(cameraInfo)
            }
        } catch (e: CameraAccessException) {
            e.printStackTrace()
        }
        return CameraInfos
    }

    //设置camera并启动
    @RequiresPermission(Manifest.permission.CAMERA)
    public fun setCameraParameters(id:Int, width:Int, height:Int, fps:Int) {
        val characteristics = cameraManager.getCameraCharacteristics(id.toString())
        fpsRange = chooseOptimalFpsRange(
            characteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)
                ?: arrayOf(),
            fps
        )
        Log.d("fpsRange","$fpsRange")
        try {
            startBackgroundThread() //启动后台进程
            imageReader = ImageReader.newInstance( //配置ImageReader
                width,
                height,
                ImageFormat.YUV_420_888,
                2
            ).apply {
                setOnImageAvailableListener({ reader ->
                    //Log.d("setOnImageAvailableListener","Active")
                    val image = reader.acquireLatestImage()
                    image?.let {
                        val buffer = it
                        frameCallback?.onFrameAvailable(buffer,it.width,it.height)
                        it.close()
                    }
                }, backgroundHandler)
            }
            cameraManager!!.openCamera(
                id.toString(),
                object : CameraDevice.StateCallback() {
                    override fun onOpened(camera: CameraDevice) {
                        Log.d("cameraManager","onOpened")
                        cameraDevice = camera
                        startPreview(width,height,fps)
                    }

                    override fun onDisconnected(camera: CameraDevice) {
                        Log.d("cameraManager","onDisconnected")
                        closeCamera()
                    }

                    override fun onError(
                        camera: CameraDevice,
                        error: Int
                    ) {
                        Log.e("cameraManager","onError:$error")
                        closeCamera()
                    }
                },
                backgroundHandler
            )
        } catch (e:CameraAccessException) {
            Log.e("setCameraParameters",e.toString())
        }
    }

    private fun startPreview(width:Int, height:Int, fps:Int) {
        try {
            val imageSurface = imageReader.surface
            val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            captureRequestBuilder.addTarget(imageSurface)
            fpsRange?.let {
                captureRequestBuilder.set(
                    CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
                    it
                )
            }
            cameraDevice.createCaptureSession(
                listOf(imageSurface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigured(session: CameraCaptureSession) {
                        captureSession = session
                        updatePreview(captureRequestBuilder)

                    }

                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        //TODO("Not yet implemented")
                    }

                },
                backgroundHandler
            )
        } catch (e:CameraAccessException) {
            Log.e("startPreview",e.toString())
        }

    }
    private fun updatePreview(requestBuilder: CaptureRequest.Builder) {
        try {
            captureSession?.setRepeatingRequest(
                requestBuilder.build(),
                null,
                backgroundHandler
            )
        } catch (e: CameraAccessException) {
            e.printStackTrace()
        }
    }
    //关闭camera
    public fun closeCamera() {
        if (::cameraDevice.isInitialized) {
            cameraDevice.close()
            stopBackgroundThread()
        }
    }
    private fun convertImageToByteArray(image: Image): ByteArray {
        val planes = image.planes
        val yBuffer = planes[0].buffer
        val uBuffer = planes[1].buffer
        val vBuffer = planes[2].buffer
        val ySize = yBuffer.remaining()
        val uSize = uBuffer.remaining()
        val vSize = vBuffer.remaining()

        val nv21 = ByteArray(ySize + uSize + vSize)
        // 复制 Y 数据
        yBuffer.get(nv21, 0, ySize)
        // 复制 UV 数据
        vBuffer.get(nv21, ySize, vSize)
        uBuffer.get(nv21, ySize + vSize, uSize)

        return nv21
    }
    // 选择最佳尺寸
    private fun chooseOptimalSize(
        choices: Array<Size>,
        width: Int,
        height: Int
    ): Size {
        val desiredRatio = width.toFloat() / height
        var optimalSize = choices[0]
        var minDiff = Float.MAX_VALUE

        for (size in choices) {
            val ratio = size.width.toFloat() / size.height
            val diff = Math.abs(ratio - desiredRatio)
            if (diff < minDiff) {
                optimalSize = size
                minDiff = diff
            }
        }

        return optimalSize
    }

    // 选择最佳 FPS 范围
    private fun chooseOptimalFpsRange(
        ranges: Array<Range<Int>>,
        desiredFps: Int
    ): Range<Int>? {
        if (ranges.isEmpty()) return null

        // 找到最接近 desiredFps 的范围
        var optimalRange: Range<Int>? = null
        var minDiff = Int.MAX_VALUE

        for (range in ranges) {
            // 检查范围是否包含 desiredFps
            if (range.lower <= desiredFps && range.upper >= desiredFps) {
                // 计算与 desiredFps 的差异
                val diff = (range.upper - desiredFps) + (desiredFps - range.lower)
                if (diff < minDiff) {
                    minDiff = diff
                    optimalRange = range
                }
            }
        }

        // 如果没有找到包含 desiredFps 的范围,返回最大范围
        return optimalRange ?: ranges.maxByOrNull { it.upper }
    }
}

此作者没有提供个人介绍。
最后更新于 2025-10-25