音视频学习记录Camera2系列一:拍照

用到的属性

//摄像头id列表
private lateinit var idList:Array
//当前摄像头id
private lateinit var cameraId:String
//当前摄像头属性类
private lateinit var cameraCharacteristics:CameraCharacteristics
//预览Surface
private lateinit var surface:Surface
//当前摄像头
private lateinit var cameraDevice: CameraDevice
//拍照输出Surface
private lateinit var imageReader:ImageReader
//预览CaptureRequest.Builder
private lateinit var captureRequestBuilder: CaptureRequest.Builder
//拍照CaptureRequest.Builder
private lateinit var photographCaptureRequest: CaptureRequest.Builder
//显示拍照图片的协程
private var mainJob:Job? = null
//Session
private lateinit var cameraCaptureSession: CameraCaptureSession
//相机分辨率
private lateinit var size:Size

初始化

override fun init() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        //初始化
        CameraUtil.init(application)
        //获取摄像头id列表
        idList = CameraUtil.getCameraIdList()
        cameraId = idList[CameraUtil.index]

        //开启surfaceTextureListener监听,获取Surface
        textureView.surfaceTextureListener = surfaceTextureListener
    }

    //拍照
    button.setOnClickListener {
        photograph()
    }

    //切换相机
    switchCamera.setOnClickListener {
        switchCamera()
    }

}

/**
 * 拍照
 */
private fun photograph(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        // 设置获取的照片方向
        if (CameraUtil.index == 0){
            CameraUtil.photoDirection(photographCaptureRequest,90)
        }else {
            CameraUtil.photoDirection(photographCaptureRequest,90*3)
        }

        //停止连续取景
        cameraCaptureSession.stopRepeating()
        //捕获单张图片
        cameraCaptureSession.capture(photographCaptureRequest.build(), captureCallback, CameraUtil.getBackgroundHandler())
    }
}

/**
 * 切换相机
 */
private fun switchCamera(){
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        cameraDevice.close()
        imageReader.close()
        //切换相机
        cameraId = CameraUtil.switchCamera(this,mStateCallback)
    }
}

获取Surface并开启摄像头

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

        //设置预设的预览尺寸
        size = CameraUtil.setDefaultBufferSize(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) {
        //获取相机属性类
        cameraCharacteristics = CameraUtil.getCameraCharacteristics(cameraId)

        //设置预设的预览尺寸
        size = CameraUtil.setDefaultBufferSize(surfaceTexture,cameraCharacteristics)

        surface = Surface(surfaceTexture)

        //开启摄像头
        CameraUtil.openCamera(this@VideoActivity,cameraId,mStateCallback)
    }
}

/**
 * 自动修改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
        }
    }
}

开始摄像头的回调

/**
 * 打开摄像头的回调
 */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private val mStateCallback = object : CameraDevice.StateCallback() {
    //成功打开时的回调,可以得到一个 CameraDevice 实例
    override fun onOpened(camera: CameraDevice) {
        cameraDevice = camera
        //拍照输出图像
        imageReader = ImageReader.newInstance(size.width, size.height, ImageFormat.JPEG,1)
        imageReader.setOnImageAvailableListener(imageAvailableListener,CameraUtil.getBackgroundHandler())

        //创建一个预览的CaptureRequest
        captureRequestBuilder = CameraUtil.createPreviewCaptureRequest(cameraDevice,surface)
        //设置相机的自动模式
        CameraUtil.automaticMode(captureRequestBuilder)

        //创建一个拍照的CaptureRequest
        photographCaptureRequest = CameraUtil.createPhotographCaptureRequest(cameraDevice,imageReader.surface)
        //设置相机的自动模式
        CameraUtil.automaticMode(photographCaptureRequest)
        //设置照片分辨率
        photographCaptureRequest.set(CaptureRequest.JPEG_THUMBNAIL_SIZE,size)

        //创建一个Session
        cameraDevice.createCaptureSession(arrayListOf(surface,imageReader.surface),mSessionCallback,CameraUtil.getBackgroundHandler())
    }

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

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

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

/**
 * imageReader的回调
 */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private val imageAvailableListener = ImageReader.OnImageAvailableListener {
    //获取接受到的图片并展示到ImageView上
    val temp = CameraUtil.getImageReaderBitmap(it)

    mainJob = GlobalScope.launch (Main){
        imageView.setImageBitmap(temp)
        //保存到系统相册
        CameraUtil.saveAlbum(temp)
    }
    // 重新开始预览
    cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), captureCallback, CameraUtil.getBackgroundHandler())
}

创建Session的回调

/**
 * 创建Session的回调
 */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private val mSessionCallback = object : CameraCaptureSession.StateCallback(){
    //
    override fun onConfigured(session: CameraCaptureSession) {
        cameraCaptureSession = session

        // 开始预览,即设置反复请求
        cameraCaptureSession.setRepeatingRequest(captureRequestBuilder.build(), captureCallback, CameraUtil.getBackgroundHandler())
    }
    //创建失败
    override fun onConfigureFailed(session: CameraCaptureSession) {

    }
}

捕捉进度回调

/**
 * Session进度的回调
 */
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
private 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)
    }
}

退出时

override fun onDestroy() {
    super.onDestroy()
    cameraDevice.close()
    mainJob?.cancel()
    imageReader.close()
    surface.release()
    CameraUtil.releaseBackgroundThread()
}

工具类

import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.SurfaceTexture
import android.hardware.camera2.*
import android.media.ImageReader
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
import android.provider.MediaStore
import android.provider.Settings
import android.support.annotation.RequiresApi
import android.support.v4.content.ContextCompat
import android.widget.Toast
import org.jetbrains.annotations.NotNull
import android.util.Size
import android.view.Surface

object CameraUtil {

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

    //摄像头id列表
    private lateinit var cameraIdList: Array

    //第几个相机
    var index = 0

    private lateinit var application: Application

    private lateinit var cameraManager: CameraManager

    /**
     * 获取cameraManager
     */
    fun getCameraManager(): CameraManager {
        return cameraManager
    }

    /**
     * 初始化
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun init(application: Application) {
        this.application = application;
        cameraManager = application.getSystemService(Context.CAMERA_SERVICE) as CameraManager
        cameraIdList = cameraManager.cameraIdList
    }

    /**
     * 获取摄像头id列表
     */
    fun getCameraIdList(): Array {
        return cameraIdList
    }

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

    /**
     * 获取预览尺寸
     * 参数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
    }

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

    /**
     * 获取预设的预览尺寸
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun getPreviewSize(@NotNull cameraCharacteristics:CameraCharacteristics):Size{
        val aspectRatios = ArrayList()
        aspectRatios.add(16.toFloat() / 9)
        aspectRatios.add(4.toFloat() / 3)
        aspectRatios.add(18.toFloat() / 9)
        return getPreviewSize(cameraCharacteristics,aspectRatios)
    }

    /**
     * 设置预设的预览尺寸
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun setDefaultBufferSize(@NotNull surfaceTexture: SurfaceTexture, cameraCharacteristics:CameraCharacteristics):Size{
        val size = getPreviewSize(cameraCharacteristics)
        surfaceTexture.setDefaultBufferSize(size.width,size.height)
        return size
    }

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

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

            }

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

            dialog.show()
        }
    }

    /**
     * 获取拍照产生的Bitmap
     */
    @RequiresApi(Build.VERSION_CODES.KITKAT)
    fun getImageReaderBitmap(it:ImageReader):Bitmap{
        val image = it.acquireLatestImage()
        val byteBuffer = image.planes[0].buffer
        val bytes = ByteArray(byteBuffer.remaining())
        byteBuffer.get(bytes)
        //清空imageReader
        image.close()
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
    }

    /**
     * 保存到相册
     */
    fun saveAlbum(temp:Bitmap){
        //保存到系统相册
        MediaStore.Images.Media.insertImage(
            application.contentResolver,
            temp,
            "image_file",
            "file")
    }

    /**
     * 创建一个预览的CaptureRequest
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun createPreviewCaptureRequest(cameraDevice:CameraDevice, surface:Surface):CaptureRequest.Builder{
        //创建一个预览的CaptureRequest
        val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
        // 设置预览输出的 Surface
        captureRequestBuilder.addTarget(surface)
        return captureRequestBuilder
    }

    /**
     * 创建一个拍照的CaptureRequest
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun createPhotographCaptureRequest(cameraDevice:CameraDevice, surface:Surface):CaptureRequest.Builder{
        //创建一个拍照的CaptureRequest
        val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
        // 设置拍照输出的 Surface
        captureRequestBuilder.addTarget(surface)
        return captureRequestBuilder
    }

    /**
     * 设置相机的自动模式
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun automaticMode(captureRequestBuilder:CaptureRequest.Builder){
        // 自动对焦
        captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
        //自动闪光
        captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON)
        //自动白平衡
        captureRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO)
    }

    /**
     * 设置获取的照片方向
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun photoDirection(captureRequestBuilder:CaptureRequest.Builder,angle:Int){
        // 根据设备方向计算设置照片的方向
        captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION,angle)
    }

    /**
     * 切换相机
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    fun switchCamera(activity: Activity, callback:CameraDevice.StateCallback):String{
        if (index < cameraIdList.size -1 ){
            index++
        }else{
            index = 0
        }
        val cameraId = cameraIdList[index]
        openCamera(activity,cameraId,callback)
        return cameraId
    }

    /**
     * 获取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("相机服务发生了一个致命错误")
            }
        }
    }

    private fun showToast(msg:String){
        Toast.makeText(application,msg,Toast.LENGTH_LONG).show()
    }

}

 

你可能感兴趣的:(安卓基础,Camera2,音视频开发)