Android-摄像头整理
前言
在实现拍照功能的时候,除了可以直接使用系统内置的拍照APP外,如果我们需要更美观的拍照功能界面,或者提供更多的操作功能,那么我们还可以选择在自己的应用程序对拍照功能进行自定义,自定义拍照界面我们仅仅需要特别关注两个类,一个是android.hardware.Camera负责调用摄像头进行操作,第二个就是android.view.SurfaceView,作用是用来预览拍照的效果。
自定义拍照流程
- 初始化摄像头
- 进行拍照(切换前后置摄像头功能、是否开启灯光功能等)
- 结束Activity.onDestroy()前,释放摄像头对象
初始化摄像头
初始化摄像头有分为很多流程,下面我们开始对摄像头进行初始化的流程梳理:
- 先判断Camera是否已经声明,如果Camera已经初始化声明过了,进行先开启预览
- 按照当前选择的前后置摄像头,获取到我们需要操作的Camera对象。(Android9以上才支持前后置摄像头的API)
- 获取到Camera对象的默认配置属性。
- 得到默认的Parameters后我们可以用一些默认配置进行修改,比如对焦类型、图片尺寸、调节手机方向和预览方向的问题、绑定SurfaceView.SurfaceHolder进行预览。
- 设置启动预览
- 为解决SurfaceView还没有初始化完成,添加监听,初始化完成后再次配置一次Camera对象
基于以上流程,我们开始进行编码:
初始开始启动预览:
private fun initCamera() {
if (mCamera != null) {
mCamera?.startPreview()
setPreviewLight()
}
.....
}
private fun setPreviewLight() {
mCamera?.setPreviewCallback(object : Camera.PreviewCallback {
override fun onPreviewFrame(data: ByteArray, camera: Camera) {
}
})
}
Camera.startPreview()表示启动摄像头的预览帧,启动后才能捕获帧显示到屏幕上,其中Camera.PreviewCallback 执行早于SurfaceHolder.setPreviewDisplay(),即可以使用Camera.PreviewCallback来做一些前置操作,比如通过获取到帧数据分析当前照片是否过暗,需要打开手电筒。
打开摄像头
private fun safeCameraOpen(id: Int): Boolean {
return try {
//释放摄像头
releaseCameraAndPreview()
mCamera = Camera.open(id)
true
} catch (e: Exception) {
Log.e("PreView", "failed to open Camera")
e.printStackTrace()
false
}
打开摄像的API为Camera.open(id),适用于Android9以上的系统,可以传入Camera.CameraInfo.CAMERA_FACING_FRONT为前置摄像头,Camera.CameraInfo.CAMERA_FACING_BACK为后置摄像头。
获取到默认的配置对象:
private fun initCamera() {
....
val parameters = mCamera?.getParameters()
....
}
Camera已经为我们设置一些默认的配置,我们可以在默认的配置对象上,具体的功能点我们下面再讲。
private fun initCamera() {
....
try {
mCamera?.setPreviewDisplay(surfaceView.holder)
} catch (e: IOException) {
e.printStackTrace()
}
....
}
绑定SurfaceView.SurfaceHolder后,我们需要再调一次开始预览。
为了解决,有时候摄像头初始化结束后,SurfaceView还没有初始化的问题,我们使用以下代码监听SurfaceView的生命周期,并再次调用摄像头的初始化流程:
private fun initCamera() {
....
val holder = surfaceView.getHolder()
if (holder != null) {
if (mCallBack != null) {
holder.removeCallback(mCallBack)
}
mCallBack = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
Log.e(TAG, "surfaceCreated$holder$this")
initCamera()
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
Log.e(TAG, "surfaceChanged$holder$this")
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.e(TAG, "surfaceDestroyed$holder$this")
}
}
holder.addCallback(mCallBack)
}
....
}
进行拍照
Camera提供了Camera.takePicture(ShutterCallback, PictureCallback , PictureCallback)进行拍照操作,其中ShutterCallback是在拍照被触发前的一段很短的时间内被触发,常用于响起快门声或其他反馈,中间的PictureCallback传回的是源文件,没有经过压缩的数据源,最后一个PictureCallback传回jpeg格式数据源。
private fun takePicture() {
mCamera?.takePicture(object :Camera.ShutterCallback{
override fun onShutter() {
}
},object :Camera.PictureCallback{
override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
}
},object:Camera.PictureCallback{
override fun onPictureTaken(data: ByteArray?, camera: Camera?) {
val bitmap=BitmapFactory.decodeByteArray(data,0,data?.size!!)
var filePath:String?=null
if (TextUtils.isEmpty(mPath)){
filePath=defaultFile().absolutePath
}else{
filePath=mPath
}
val path=FileUtils.saveBitmap2File(bitmap,filePath)
val intent = Intent()
intent.putExtra("data", path)
setResult(Activity.RESULT_OK,intent)
finish()
}
})
}
释放资源
为了防止内存泄漏,需要释放Camera对象,一般在onDestory()方法中调用:
private fun releaseCameraAndPreview() {
mCamera=null
mCamera?.also { camera ->
camera.release()
mCamera = null
}
}
拍照属性配置功能点详解
设置摄像头的配置需要通过如下步骤:
- 获取默认的配置对象Camera?.getParameters()
- 设置配置前判断当前设备是否支持配置parameters?.getSupportedFocusModes()?,如果不支持会返回null
- 设置完成后调用**Camera?.setParameters(parameters)**才回生效
摄像头的常用配置如下:
是否自动对焦
if (parameters?.getSupportedFocusModes()?.contains(
Camera.Parameters
.FOCUS_MODE_CONTINUOUS_PICTURE
)!=null
) {
//自动持续对焦
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)
}
这段代码设置了应用于拍照的对焦模式,有一下几种模式进行参考:
- FOCUS_MODE_AUTO 适用于任何操作的自动对焦模式
- FOCUS_MODE_CONTINUOUS_PICTURE 适用于拍照的自动对焦模式
- FOCUS_MODE_CONTINUOUS_VIDEO 适用于拍视频的自动对焦模式
- FOCUS_MODE_EDOF 扩展景深模式
- FOCUS_MODE_FIXED 固定焦点模式
- FOCUS_MODE_INFINITY 焦点无限远
- FOCUS_MODE_MACRO 特写模式
设置JPEG图片格式的GPS信息
当我们拍照或者摄影的时候,在JPEG格式的信息上,需要拍摄具有经纬度的图片,可以使用如下设置进行设置GPS信息:
- parameters.setGpsLatitude 设置纬度
- parameters.setGpsLongitude 设置经度
- parameters.setGpsAltitude 设置海拔
- parameters.setGpsTimestamp 设置时间戳
- parameters.setGpsProcessingMethod 设置GPS处理方法
- parameters.removeGpsData 清除之前设置的GPS信息
白平衡
- WHITE_BALANCE_AUTO 自动白平衡
- WHITE_BALANCE_INCANDESCENT 白织灯
- WHITE_BALANCE_FLUORESCENT 荧光灯
- WHITE_BALANCE_WARM_FLUORESCENT 温暖的荧光灯
- WHITE_BALANCE_DAYLIGHT 充满阳光的天气
- WHITE_BALANCE_CLOUDY_DAYLIGHT 阴天、日光
- WHITE_BALANCE_TWILIGHT 暮光
- WHITE_BALANCE_SHADE 阴凉处
设置预览的图片大小
不同的手机支持的摄像头图片大小不同,这就要求我们能够通过一定的算法,选出能够被设备支持并且合适的预览大小。
// 获取到设备支持的图片大小
val picSizes = parameters?.getSupportedPictureSizes()
private fun getPictureSize(picSizes: List<Camera.Size>?, width: Int, height: Int): Camera.Size? {
// 对于存储最适合的最小
var betterSize: Camera.Size? = null
// 差值
var diff = Integer.MAX_VALUE
if (picSizes != null && picSizes.size > 0) {
for (size in picSizes) {
val newDiff = Math.abs(size.width - width) + Math.abs(size.height - height)
// 如果没有任何差别、说明是最合适的,直接返回,减少遍历
if (newDiff == 0) {
return size
}
if (newDiff < diff) {
betterSize = size
diff = newDiff
}
}
}
return betterSize
}
通过以上对比遍历后,就能索引出最适合的图片预览大小,然后直接设置到Camera.Parameter]中即可:
parameters?.setPictureSize(picSize!!.width, picSize.height)
更多详情,请查阅Camera.Parameter
相机方向和预览方向的适配
略