文章目录
1. 介绍
本文档是对于 android.hardware.Camera 的一个使用说明文档,主要帮助自己加强对于Camera的理解和使用,同时也是对Camera上层代码的源码进行一个翻译,说明各个方法的作用以及一些参数的实际效果,为以后能快速使用Camera进行开发提供一份详细的参考文档。
本文档适用于初、中级的Camera方向的研发人员。可以作为快速入门的文档,引导和帮助大家更好的了解源码,同时也可以作为在开发过程中的工具书。由于没有深入jni层研究,所以在一些方面难免存在瑕疵,希望大家多多交流和指正。
2. Camera 详解
大家首先要注意,我们使用的Camera的包路径是android.hardware.Camera,所以在使用时需要注意不要把路径搞错了。
Camera是Android通过Java层调用系统相机的类,对于Camera的实际操作都是驱动层写好了的,Java层只能进行的一些简单的操作,包括常用的配置、常用的功能以及一些数据、状态的回调。Camera可供我们调用的常用方法如下:
- open() : 打开摄像头
- getParameters() : 获取Camera.Parameters
- setParameters() : 设置Camera.Parameters
- setPreviewDisplay() : 设置用于预览图输出的SurfaceView
- setPreviewTexture() : 设置用于预览图输出的SurfaceTexture
- startPreview() : 开始预览
- stopPreview() : 结束预览
- setPreviewCallback() : 设置预览的回调
- setOneShotPreviewCallback() : 设置下一帧预览图像的回调
- takePicture() : 拍照,并回调结果到传入的Callback中
- autoFocus() : 进行一次自动对焦并回调
- cancelAutoFocus() : 取消自动变焦
- setAutoFocusMoveCallback() : 设置自动对焦时的回调
- startSmoothZoom() : 设置焦距,动态改变
- stopSmoothZoom() : 关闭设置焦距时的动态
- setZoomChangeListener() : 设置变焦的监听
- setDisplayOrientation() : 设置预览图的旋转角度(0,90,180,270)
- enableShutterSound() : 打开/关闭拍照时的声音
- startFaceDetection() : 开启人脸识别
- stopFaceDetection() : 关闭人脸识别
- setFaceDetectionListener() : 设置人脸识别的监听
- release() : 释放设备
在这些方法中,都没有太复杂的逻辑,不过关于Camera的使用,有些特点我们需要注意:
- Camera是系统唯一的,如果你的App占用了Camera,其它App将不能再通过调用open()方法打开摄像头。所以,我们需要在App进入后台状态,或者退出相机界面后,调用Camera.release()将Camera相关的资源释放掉。
- open()只是打开了摄像头,并没有开始预览,所以在startPreview()之前,PreviewCallback.onPreviewFrame()不会调用。
- setOneShotPreviewCallback()设置的回调只会设置一次调用一次。
- 如果open()和startPreview()在短时间内顺序执行了,可能会出现开始预览失败的情况。
3. Camera.Params
Camera.Params提供了Java上层能对摄像机参数进行的全部配置,也是我们在使用相机时最需要去了解的内容。从源码中我们可以了解到,Params内部维护了一个LinkedHashMap,而我们进行的一些配置,都是通过put方法放入到map中去的,它提供了很多KEY供给用户进行设置,也会有一些没有公开定义的Key,我们可以通过拼接的方式完成,比如:
获取支持的分辨率熟悉的key :KEY_PREVIEW_SIZE + SUPPORTED_VALUES_SUFFIX
可能还有一些我们不知道的key可以对摄像头进行配置的,就需要我们自己去网上找解决方案。
下面,会针对这个类里面的已有的全部参数进行说明。
3.1 预览图大小
设置预览图大小需要调用的方法一般有下面几个方法:
- params.getSupportedPreviewSizes():获取手机支持的预览效果的分辨率
- params.setPreviewSize(width,height):设置预览效果的分辨率
由于手机一般是支持多种分辨率的,所以我们在调用setPreviewSize之前,需要找到和我们屏幕适配的预览大小,具体方法如下:
private static Point findBestPreviewSizeValue(List<Camera.Size> supportSizeList, Point screenResolution) {
int bestX = 0;
int bestY = 0;
int diff = Integer.MAX_VALUE;
for (Camera.Size previewSize : supportSizeList) {
int newX = previewSize.width;
int newY = previewSize.height;
int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
if (newDiff == 0) {
bestX = newX;
bestY = newY;
break;
} else if (newDiff < diff) {
bestX = newX;
bestY = newY;
diff = newDiff;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}
return null;
}
如果使用的全屏手机,可能还是会存在预览图片变形的情况。那是因为找到的预览大小和Surface大小不一致导致的,我们要重写SurfaceView,在onMeasure()方法中给我们的控件设置一个合适的大小:
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
int height = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
if (mCameraConfigurationManager != null && mCameraConfigurationManager.getCameraResolution() != null) {
Point cameraResolution = mCameraConfigurationManager.getCameraResolution();
int cameraPreviewWidth = cameraResolution.x;
int cameraPreviewHeight = cameraResolution.y;
if (width * 1f / height < cameraPreviewWidth * 1f / cameraPreviewHeight) {
float ratio = cameraPreviewHeight * 1f / cameraPreviewWidth;
width = (int) (height / ratio + 0.5f);
} else {
float ratio = cameraPreviewWidth * 1f / cameraPreviewHeight;
height = (int) (width / ratio + 0.5f);
}
}
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
}
特别注意:如果我们预览界面是在竖屏状态下(一般就是竖屏),系统的预览大小设置都是横屏状态下的宽高,所以我们需要将宽高互换。在上面的方法中,mCameraConfigurationManager.getCameraResolution()得到的就是互换之后的预览图宽高。
3.2 预览图帧率
帧率就是以帧为单位的位图图像连续出现在显示器上的频率(一般叫FPS),Params提供了下面一些方法对帧率进行配置:
- getSupportedPreviewFrameRates():获得系统支持的帧率。
- setPreviewFrameRate():设置固定的FPS。
- setPreviewFrameRateRange():设置FPS的范围。
- getSupportedPreviewFpsRange() : 同getSupportedPreviewFrameRates
- setPreviewFpsRange() : 同setPreviewFpsRange
一般情况下,对于没有录像要求的时候,不建议使用手动设置帧率,因为帧率过高,会导致手机耗电严重,因为需要GPU去处理的数据会剧增。
比如:当画面的分辨率是1024×768时,画面的刷新率要达到24帧/秒,那么显卡在一秒钟内需要处理的像素量就达到了“1024×768×24=18874368”。如果要求画面的刷新率达到50帧/秒,则数据量一下子提升到了“1024×768×50=39321600”。
3.3 白平衡
白平衡是指的相机将白色还原的能力,在我们需要控制照片拍出来的颜色的时候用到。一般情况我们不会手动设置白平衡,如果发现我们拍出来的照片的颜色与我们看到的有差别,就可能通过调整白平衡来还原颜色。下面是和白平衡相关的设置方法和属性:
- isAutoWhiteBalanceLockSupported() : 判断机器是否支持锁定自动白平衡
- setAutoWhiteBalanceLock() : 锁定自动白平衡
- setWhiteBalance() : 设置白平衡,如果白平衡设置有变动,会解除自动白平衡的锁定。
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 :阴天
3.4 特效/滤镜(少用)
特效是对我们的预览图片进行一些特效处理的手段,也可以理解成系统提供给我们的滤镜,比如黑白照片等。系统提供的效果是很难达到我们的正常需要的,所以如果真的需要做滤镜,可以通过OpenGL或者OpenCV对预览图进行处理,这里不做详述。下面是和特效相关的设置方法和熟悉:
- getSupportedColorEffects()
- setColorEffect()
EFFECT_NONE :没有特效
EFFECT_MONO
EFFECT_NEGATIVE
EFFECT_SOLARIZE
EFFECT_SEPIA
EFFECT_POSTERIZE
EFFECT_WHITEBOARD
EFFECT_BLACKBOARD
EFFECT_AQUA
3.5 曝光频率(少用)
CMOS是相机的核心成像传感器,它是分行曝光的,不同行,曝光的起始时间点并不相同,这就可能导致在某些场景下我们的预览效果中可能不同行的亮度有明显差异。设置这个频率可以理解成传感器对于用于曝光的能量进行采集的速率,一般我们不会设置这个参数。下面是系统提供能一些方法和属性:
- getSupportedAntibanding()
- setAntibanding()
ANTIBANDING_AUTO
ANTIBANDING_50HZ
ANTIBANDING_60HZ
ANTIBANDING_OFF
3.6 闪光灯
闪光灯是我们在相机开发中常用的一个器件,闪光灯的配置不仅仅是打开和关闭那么简单,它也有自己的模式,系统提供了下面一些方法和属性:
- getSupportedFlashModes()
- setFlashMode()
FLASH_MODE_AUTO :自动模式
FLASH_MODE_OFF : 关闭
FLASH_MODE_ON : 开启
FLASH_MODE_RED_EYE : 降低红眼模式
FLASH_MODE_TORCH :手电筒模式,持续开启闪光灯
由于有些小伙伴可能不知道什么是红眼模式,所以介绍下:
“红眼”这个术语实际上是针对人物拍摄的,当闪光灯照射到人眼的时候,瞳孔会放大让更多的光线通过,视网膜的血管就会在照片上产生泛红现象。由于红眼现象的程度是根据拍摄对象色素的深浅决定的,如果拍摄对象的眼睛颜色较深,红眼现象便不会特别明显。
3.7 场景
场景就是系统针对一些特定的场景,有一套固定的配置,设置场景会改变其它你已经进行的设置,比如白平衡、曝光值等。系统提供了下面一些方法和属性:
- getSupportedSceneModes()
- setSceneMode()
SCENE_MODE_AUTO : 关闭此模式
SCENE_MODE_ACTION:适用于运动物体场景
SCENE_MODE_BARCODE : 适用于条形码场景
SCENE_MODE_BEACH : 适用于海滩场景
SCENE_MODE_CANDLELIGHT:适用于烛光场景
SCENE_MODE_FIREWORKS : 适用于烟火表演场景
SCENE_MODE_HDR : 高动态范围图像
SCENE_MODE_LANDSCAPE : 远景拍摄
SCENE_MODE_NIGHT : 夜间场景
SCENE_MODE_NIGHT_PORTRAIT : 夜间人物场景
SCENE_MODE_PARTY : 室内弱光环境
SCENE_MODE_PORTRAIT : 人像模式
SCENE_MODE_SNOW : 拍摄雪景
SCENE_MODE_SPORTS : 类似SCENE_MODE_ACTION
SCENE_MODE_STEADYPHOTO : 固定物体拍摄,避免模糊,比如可以削弱手抖影响
SCENE_MODE_SUNSET : 日落场景
SCENE_MODE_THEATRE : 剧院场景,关闭闪光灯
上面这些场景中,介绍下HDR:
HDR,全称为:High-Dynamic Range,翻译成中文就是:高动态范围图像。相比普通的图像,可以提供更多的动态范围和图像细节,能够更好的反映出真实环境中的视觉效果。原理就是通过对多张不同曝光值的图片进行合成,生成更好的成像细节。
3.8 对焦&变焦
摄像头对焦的方式,可以分为手动和自动两种方式。其中的变焦相关的方法,包括:
- isZoomSupported() :是否支持改变焦距
- getMaxZoom() :获取最大焦距
- getZoom() :获得焦距
- setZoom() :设置焦距
- getZoomRatios() :获取焦距和放大比例的映射表
- isSmoothZoomSupported() :是否支持动态变焦
- startSmoothZoom() :开始动态变焦
- stopSmoothZoom() :结束动态变焦
这些方法中,动态变焦我们用的不多,这里不多介绍。
关于自动对焦
我们需要的是为自动对焦设置一种模式,相关的方法和参数如下:
- getSupportedFocusModes() :获得可以支持的对焦类型
- setFocusMode() :设置对焦类型
//常用的对焦方式:
FOCUS_MODE_AUTO :自动对焦
FOCUS_MODE_CONTINUOUS_VIDEO :持续对焦模式,适用录制视频。此模式可调用autoFocus(AutoFocusCallback)
FOCUS_MODE_CONTINUOUS_PICTURE :持续对焦模式,适用于拍照。此模式可调用autoFocus(AutoFocusCallback)
//不常用的对焦方式
FOCUS_MODE_MACRO :宏观对焦模式,此模式可调用autoFocus(AutoFocusCallback)
FOCUS_MODE_INFINITY :无穷模式,此模式不能调用autoFocus(AutoFocusCallback)
FOCUS_MODE_FIXED :适用于超焦距对焦,此模式不能调用autoFocus(AutoFocusCallback)
FOCUS_MODE_EDOF :Extended depth of field (EDOF),此模式不能调用autoFocus(AutoFocusCallback)
//下面三个参数是用来获取对焦物体到摄像头的距离的,实测没用!
FOCUS_DISTANCE_NEAR_INDEX
FOCUS_DISTANCE_OPTIMAL_INDEX
FOCUS_DISTANCE_FAR_INDEX
关于自动对焦&变焦,我们需要注意的一个方法是getZoomRatios。这个方法会返回一个数组,如果你的MAX_ZOOM是90,那么返回的数组大小就是90,数组内的值就是 放大倍数*1000,所以,我们可以通过图形在我们预览图的占比,参考这个方法返回的倍数,来设置对于的焦距。(使用时注意先获得当前的zoom和当前的放大倍数)
关于手动对焦
我们需要的是设置一个对焦区域,具体的方法可以参考如下代码:
/**
* 手动对焦 - 对焦坐标转化的比例
*/
private static final int FOCUS_MANUAL_AREA_RATIO = 1000;
/**
* 手动对焦 - 对焦区域大小
*/
private static final int FOCUS_MANUAL_AREA_SIZE = 150;
/**
* 请求一次手动对焦
* @param downX 按下的点在屏幕上的x坐标
* @param downY 按下的点在屏幕上的y坐标
*/
public synchronized void requestAManualFocus(float downX, float downY){
Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
float ratioX = 0;
float ratioY = 0;
if(QRCodeUtil.getOrientation(getContext()) == QRCodeUtil.ORIENTATION_PORTRAIT) {
ratioX = downY / previewSize.width;
ratioY = 1 - downX / previewSize.height;
}else {
ratioX = downX / previewSize.width;
ratioY = downY / previewSize.height;
}
int centerX = (int)(ratioX*2*FOCUS_MANUAL_AREA_RATIO - FOCUS_MANUAL_AREA_RATIO);
int centerY = (int)(ratioY*2*FOCUS_MANUAL_AREA_RATIO - FOCUS_MANUAL_AREA_RATIO);
startFocusByArea(getFocusArea(centerX,centerY));
}
/**
* 获得对焦区域
*
* @param x 用户点击的坐标
* @param y 用户点击的坐标
* @return
*/
private Camera.Area getFocusArea(int x, int y){
int left = x - FOCUS_MANUAL_AREA_SIZE;
int top = y - FOCUS_MANUAL_AREA_SIZE;
Rect focusArea = new Rect();
focusArea.left = Math.max(left, -FOCUS_MANUAL_AREA_RATIO);
focusArea.top = Math.max(top, -FOCUS_MANUAL_AREA_RATIO);
focusArea.right = Math.min(left + FOCUS_MANUAL_AREA_SIZE, FOCUS_MANUAL_AREA_RATIO);
focusArea.bottom = Math.min(top + FOCUS_MANUAL_AREA_SIZE, FOCUS_MANUAL_AREA_RATIO);
return new Camera.Area(focusArea, 2* FOCUS_MANUAL_AREA_SIZE);
}
private void startFocusByArea(Camera.Area cameraArea){
Zlog.log(cameraArea.rect.toString());
Camera.Parameters mParams = mCamera.getParameters();
List<Camera.Area> meteringAreas = null;
List<Camera.Area> focusAreas = null;
if (mParams.getMaxNumMeteringAreas() > 0) {
meteringAreas = new ArrayList<Camera.Area>();
meteringAreas.add(cameraArea);
}
if (mParams.getMaxNumMeteringAreas() > 0) {
focusAreas = new ArrayList<Camera.Area>();
focusAreas.add(cameraArea);
}
if(focusAreas != null){
mParams.setFocusAreas(focusAreas);
}
if(meteringAreas != null) {
mParams.setMeteringAreas(meteringAreas);
}
mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
requestAAutoFocus(0);
}
关于手动变焦
我们需要的是根据我们两根手指在屏幕上滑动的相对距离,设置焦距,具体实现可以参考下面代码:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
startDis = distance(event);
break;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() < 2) {
break;
}
float endDis = distance(event);
int scale = (int) (endDis - startDis);
if(scale > 0) {
setZoomByFinger(curZoom + 1);
} else {
setZoomByFinger(curZoom - 1);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
default:
break;
}
super.onTouchEvent(event);
return true;
}
更多有趣内容,欢迎关注个人公众号(i码男):