开发Android Camera—使用Kotlin语言,完成第一个自定义相机

对于首次使用Kotlin语言开发,在网上苦于寻找不到Kotlin语言编写的相机代码,故写下这篇博客。
好了,咱们进入主题

在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架构上做了巨大的变动,但是基于众所周知的原因,我们还必须基于 Android 4.+ 系统进行开发。本文介绍的是Camera接口开发及其使用方法,通过本文章,你将全面地学会Camera接口的开发流程。

开发步骤

  • 添加相机相关权限
  • 通过Camera.open(int)获得一个相机实例,也可以使用默认的Camera.open()
  • 利用camera.parameters得到相机实例的默认设置Camera.parameters
  • 如果需要的话,修改Camera.parameters并调用camera.parameters=Camera.Parameters来修改相机设置
  • 调用camera.setDisplayOrientation(int)来设置正确的预览方向
  • 调用camera.setPreviewDisplay(SurfaceHolder)来设置预览,如果没有这一步,相机是无法开始预览的
  • 调用camera.startPreview()来开启预览,对于拍照,这一步是必须的
  • 在需要的时候调用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)来拍摄照片
  • 拍摄照片后,相机会停止预览,如果需要再次拍摄多张照片,需要再次调用camera.startPreview()来重新开始预览
  • 调用camera.stopPreview()来停止预览
  • 一定要在onPause()的时候调用camera.release()来释放camera,在onResume中重新开始camera

相机权限

在使用相机进行拍照,需要添加以下权限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

拍摄的照片需要存储到内存卡的话,还需要内存卡读写的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

拍照注意事项

根据拍照步骤就可以使用相机进行拍照,但是在使用过程中,有诸多需要注意的地方。

开启相机

开启相机直接调用Camera.open(),理论上就可以得到一个相机实例。但是在开启相机前,最好check一下。虽然目前基本上所有的手机都是前后摄像头,但是也不排斥有些奇葩的手机,只有后摄像头,或者干脆没有摄像头的。所有在open一个camera前,先检查是否存在该id的camera。Camera.getNumberOfCameras()可以获得当前设备的Camera的个数N,N为0表示不支持摄像头,否则对应的Camera的ID就是0—(N-1)。

检查是否支持相机:

    private fun isCameraAvailable(): Boolean {
        val numberOfCameras = Camera.getNumberOfCameras()
        if (numberOfCameras != 0) {
            return true
        }
        return false
    }

打开相机:

   private fun getCamera(): Camera? {
        var camera: Camera? = null
        try {
            camera = Camera.open()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return camera
    }

相机设置

Camera的Parameters提供了诸多属性,可以对相机进行多种设置,包括预览大小及格式、照片大小及格式、预览频率、拍摄场景、颜色效果、对焦方式等等,具体设置可参考官方手册。
拍照尤其需要注意的是对预览大小、照片大小以及对焦方式的设置。
在对预览大小、照片大小及对焦方式设置时,设置的值必须是当前设备所支持的。否则,预览大小和照片大小设置会无效,对焦方式设置会导致崩溃。它们都有相应的方法,获取相应的支持的列表。对应的依次为getSupportedPictureSizes(),getSupportedPictureSizes(),getSupportedFocusModes()。

相机设置代码:

private fun setParameters(camera: Camera?) {
        camera?.let {
            val params: Camera.Parameters = camera.parameters
            params.pictureFormat = ImageFormat.JPEG
            val size = Collections.max(params.supportedPictureSizes, object : Comparator<Camera.Size> {
                override fun compare(lhs: Camera.Size, rhs: Camera.Size): Int {
                    return lhs.width * lhs.height - rhs.width * rhs.height
                }
            })
            params.setPreviewSize(size.width, size.height);  
            params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
            try {
                camera.parameters = params;    //在设置属性时,如果遇到未支持的大小时将会直接报错,故需要捕捉一样,做异常处理
            } catch (e: Exception) {
                e.printStackTrace()
                try {
                    //遇到上面所说的情况,只能设置一个最小的预览尺寸
                    params.setPreviewSize(1920, 1080);
                    camera.parameters = params;
                } catch (e1: Exception) {
                    //到这里还有问题,就是拍照尺寸的锅了,同样只能设置一个最小的拍照尺寸
                    e1.printStackTrace();
                    try {
                        params.setPictureSize(1920, 1080);
                        camera.parameters = params;
                    } catch (ignored: Exception) {}}}
              }
    }

相机预览方向和预览View的设置

不对相机预览方向和应用方向设置,通常情况下得到的预览结果是无法接受的。一般应用设置的方向为固定竖向,预览设置旋转90度即可。严谨点来说,预览方向的设置是根据当前window的rotation来设置的,即((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在Surface.ROTATION_0和Surface.ROTATION_180时,Camera设置displayOrientation为90,否则设置为0。
相机预览前,必须调用camera.setPreviewDisplay(SurfaceHolder)来设置预览的承载。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情况下,如果不对显示的View大小做合理的设置,预览中的场景都会被变形。

添加SurfaceHolder的Callback监听

 val holder = sv_camera.holder
        holder?.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            }

            override fun surfaceDestroyed(holder: SurfaceHolder?) {
            }

            override fun surfaceCreated(holder: SurfaceHolder?) {
                setStartPreview(mCamera, holder);
            }
        })

设置预览的方向:

  private fun setStartPreview(camera: Camera?, holder: SurfaceHolder?) {
        camera?.let {
            try {
                camera.setPreviewDisplay(holder)
                camera.setDisplayOrientation(Surface.ROTATION_90)
                camera.startPreview()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

拍照监听及图片处理

相机拍照时在预览时,调用takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重载方法)来实现拍照的,其中第一个参数表示图像捕获时刻的回调。可以看到此方法的后三个参数类型是一样的,都是图像回调。分别表示原始图数据回调、展示图像数据的回调、JPEG图像数据的回调。图像回调中得到byte数组decode为image后,图片的方向都是摄像头原始的图像方向。
可以通过parameters.setRotation(int)来改变最后一个回调中图像数据的方向。个人推荐不设置,直接在回调中利用矩阵变换统一处理。因为利用parameters.setRotation(int)来旋转图像,在不同手机上会有差异。
与预览View设置类似,pictureSize设置的值,影响了最后的拍照结果,处理时需要对拍照的结果进行裁剪,使图片结果和在可视区域预览的结果相同。前摄像头拍摄的结果还需要做对称变换,以保证“所见即所得”。

拍照监听:

    private fun taskPicture() {
        mCamera?.autoFocus { success, camera ->
            run {
                mCamera?.takePicture(null, null, pictureCallback);
            }
        }
    }

图片处理:

 val pictureCallback = Camera.PictureCallback { data, camera ->
        val pictureFile = File(Environment.getExternalStorageDirectory(), "gaozhongkui-" + System.currentTimeMillis() + ".jpg")
        try {
            val fos = FileOutputStream(pictureFile)
            fos.write(data)
            fos.close();
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

以下是完成的代码实例:

xml布局:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <SurfaceView
        android:id="@+id/sv_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/btn_capture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="拍照" />
</FrameLayout>

代码逻辑:

import android.graphics.ImageFormat
import android.hardware.Camera
import android.os.Bundle
import android.os.Environment
import android.support.v7.app.AppCompatActivity
import android.view.Surface
import android.view.SurfaceHolder
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.util.*
import kotlin.Comparator

class MainActivity : AppCompatActivity() {

    var mCamera: Camera? = null
    val pictureCallback = Camera.PictureCallback { data, camera ->
        val pictureFile = File(Environment.getExternalStorageDirectory(), "gaozhongkui-" + System.currentTimeMillis() + ".jpg")
        try {
            val fos = FileOutputStream(pictureFile)
            fos.write(data)
            fos.close();
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (isCameraAvailable()) {
            mCamera = getCamera()
            setParameters(mCamera);
        }
        initViewListener();

    }

    private fun  initViewListener(){
        val holder = sv_camera.holder
        holder?.addCallback(object : SurfaceHolder.Callback {
            override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
            }

            override fun surfaceDestroyed(holder: SurfaceHolder?) {
            }

            override fun surfaceCreated(holder: SurfaceHolder?) {

                setStartPreview(mCamera, holder);
            }

        })
        btn_capture.setOnClickListener {
            taskPicture()
        }
    }

    private fun isCameraAvailable(): Boolean {
        val numberOfCameras = Camera.getNumberOfCameras()
        if (numberOfCameras != 0) {
            return true
        }
        return false
    }


    private fun getCamera(): Camera? {
        var camera: Camera? = null
        try {
            camera = Camera.open()
        } catch (e: Exception) {
            e.printStackTrace()
        }

        return camera
    }

    fun releaseCamera() {
        mCamera?.setPreviewCallback(null)
        mCamera?.stopPreview()
        mCamera?.release()
        mCamera = null
    }


    private fun setStartPreview(camera: Camera?, holder: SurfaceHolder?) {
        camera?.let {
            try {
                camera.setPreviewDisplay(holder)
                camera.setDisplayOrientation(Surface.ROTATION_90)
                camera.startPreview()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    private fun setParameters(camera: Camera?) {
        camera?.let {
            val params: Camera.Parameters = camera.parameters
            params.pictureFormat = ImageFormat.JPEG

            val size = Collections.max(params.supportedPictureSizes, object : Comparator<Camera.Size> {
                override fun compare(lhs: Camera.Size, rhs: Camera.Size): Int {
                    return lhs.width * lhs.height - rhs.width * rhs.height
                }
            })
            params.setPreviewSize(size.width, size.height);

            params.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
            try {
                camera.parameters = params;
            } catch (e: Exception) {
                e.printStackTrace()
                try {
                    //遇到上面所说的情况,只能设置一个最小的预览尺寸
                    params.setPreviewSize(1920, 1080);
                    camera.parameters = params;
                } catch (e1: Exception) {
                    //到这里还有问题,就是拍照尺寸的锅了,同样只能设置一个最小的拍照尺寸
                    e1.printStackTrace();
                    try {
                        params.setPictureSize(1920, 1080);
                        camera.parameters = params;
                    } catch (ignored: Exception) {
                    }
                }

            }
        }
    }

    private fun taskPicture() {
        mCamera?.autoFocus { success, camera ->
            run {
                mCamera?.takePicture(null, null, pictureCallback);
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_37618354/article/details/84248914