前言
遇到Senl大佬给我的一个项目,在此项目中需要通过传感器来获得当时的一些信息,并传递给下一个Activity。于是便开始学习Camera Api的使用
参考文章链接:点击此处
在Android 5.0(SDK 21)中,Google使用Camera2替代了Camera接口。Camera2在接口和架构上做了巨大的变动, 但是基于一些原因,我们还必须基于 Android 4.+ 系统进行开发。本文介绍的是Camera接口的使用方法
使用Camera开发照相功能
1.在Android Manifest.xml中声明权限
首先要声明使用相机的权限以及写入储存器的权限:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WEITE_EXTERNAL_STORAGE" />
另外,要加上两条特性声明:
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
required属性是说明这个特性是否必须满足。比方说示例的设置就是要求必须拥有相机设备但可以没有自动对焦功能。
这两个声明是可选的,它们用于应用商店(Google Play)过滤不支持相机和不支持自动对焦的设备。
2.打开相机设备
现在市面上销售的手机/平板基本标配两个摄像头。我们可以在打开相机设备前,先获取当前设备有多少个相机设备。如果开发需求有需要切换摄像头功能的时候,可以通过获取摄像头数量来判断是否有后置摄像头。
int cameras = Camera.getNumberOfCameras();
这个接口返回值为摄像头的数量:非负整数。摄像头序号为: cameras – 1。例如在拥有前后摄像头的手机设备上,其返回结果是2,则第一个摄像头的cameraId是0,通常对应手机背后那个大摄像头;第二个摄像头的cameraId是1,通常对应着手机的前置自拍摄像头;
相机是一个硬件设备资源,在使用设备资源前需要将它打开,可以通过接口Camera.open(cameraId)来打开。代码如下:
public static Camera openCamera(int cameraId) {
try{
return Camera.open(cameraId);
}catch(Exception e) {
return null;
}
}
注意
打开相机设备可能会失败,一定要检查打开操作是否成功。打开失败的可能原因有两种:一是安装App的设备上根本没有摄像头,例如某些平板或特殊Android设备;二是cameraId对应的摄像头正被使用,可能某个App正在后台使用它录制视频。
3.配置相机参数
打开相机设备后,会获得一个Camera对象,并独占相机设备资源。 通过Camera.getParameters()接口可以获取当前相机设备的默认配置参数。下面列举一些参数:
闪光灯配置参数,可以通过Parameters.getFlashMode()接口获取当前相机的闪光灯配置参数:
- Camera.Parameters.FLASH_MODE_AUTO 自动模式,当光线较暗时自动打开闪光灯
- Camera.Parameters.FLASH_MODE_OFF 关闭闪光灯
- Camera.Parameters.FLASH_MODE_ON 拍照时闪光灯
- Camera.Parameters.FLASH_MODE_RED_EYE 闪光灯参数,防红眼模式
对焦模式配置参数,可以通过Parameters.getFocusMode()接口获取:
- Camera.Parameters.FOCUS_MODE_AUTO 自动对焦模式
- Camera.Parameters.FOCUS_MODE_FIXED 固定焦距模式
- Camera.Parameters.FOCUS_MODE_EDOF 景深模式
- Camera.Parameters.FOCUS_MODE_INFINITY 远景模式
- Camera.Parameters.FOCUS_MODE_MACRO 微焦模式
场景模式配置参数,可以通过Parameters.getSceneMode()接口获取:
- Camera.Parameters.SCENE_MODE_BARCODE 扫描条码场景
- Camera.Parameters.SCENE_MODE_ACTION 动作场景
- Camera.Parameters.SCENE_MODE_AUTO 自动选择场景
- Camera.Parameters.SCENE_MODE_HDR 高动态对比度场景
- Camera.Parameters.SCENE_MODE_NIGHT 夜间场景
Camera API提供了非常多的参数接口供开发者设置,有必要的话,可以翻阅相关API文档。
4.设置相机预览方向
相机预览图需要设置正确的预览方向才能正常地显示预览画面,否则预览画面会被挤压。通常情况下,如果我们需要知道设备的屏幕方向,可以通过Resources.Configuration.orientation来获取。Android屏幕方向有“竖屏”和“横屏”两种,对应的值分别是ORIENTATION_PORTRAIT和ORIENTATION_LANDSCAPE。
但相机设备的方向却有些特别,设置预览方向的接口Camera.setDisplayOrientaion(int)的参数是以角度为单位的,而且只能是0,90,180,270其中之一,默认为0,是指手机的左侧为摄像头顶部画面。记得只能是[0、90、180、270]其中之一,输入其它角度数值会报错。
如果想让相机跟随设备的方向,预览界面顶部一直保持正上方,以下代码供参考
public static void followScreenOrientation(Context context, Camera camera){
final int orientation = context.getResources().getConfiguration().orientation;
if(orientation == Configuration.ORIENTATION_LANDSCAPE) {
camera.setDisplayOrientation(180);
}else if(orientation == Configuration.ORIENTATION_PORTRAIT) {
camera.setDisplayOrientation(90);
}
}
5.预览View与拍照
我们一般用SurfaceView作为相机预览的View,也可以使用Texture。在SurfaceView中获取得SurfaceHolder,并通过setPreviewDisplay()接口设置预览。
在设置预览View后,一定要记得以下三点:
- 调用startPreview()方法启动预览,否则预览View不会显示任何内容
- 拍照操作需要在startPreview()方法执行之后调用;
- 每次拍照后,预览View会停止预览。所以连续拍照,需要重新调用startPreview()来恢复预览
Camera接受一个SurfaceHolder接口,这个接口可以通过SurfaceHolder.Callback获得。我们可以通过继承SurfaceView来实现相机预览效果。
下面实现了CameraPreviewView类,它内部已实现了相机预览所需要的处理过程,以下是它的全部源码:
public class CameraPreviewView extends SurfaceView implements SurfaceHolder.Callback{
private Camera camera;
private SurfaceHolder surfaceHolder;
public CameraPreviewView(Context context, AttributeSet attrs,int defStyleAttr){
super(context,attrs,defStyleAttr);
surfaceHolder = this.getHolder();
surfaceHolder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
//启动预览
startPreviewDisplay(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (surfaceHolder.getSurface() == null){
return;
}
//重新恢复预览
stopPreviewDisplay();
startPreviewDisplay(holder);
}
public void setCamera(Camera theCamera) {
camera = theCamera;
final Camera.Parameters params = camera.getParameters();
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
params.setSceneMode(Camera.Parameters.SCENE_MODE_BARCODE);
}
private void startPreviewDisplay(SurfaceHolder holder){
checkCamera();
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
private void stopPreviewDisplay(){
checkCamera();
try {
camera.stopPreview();
} catch (Exception e){
e.printStackTrace();
}
}
private void checkCamera(){
if(camera == null) {
throw new IllegalStateException("Camera must be set when start/stop preview, call <setCamera(Camera)> to set");
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopPreviewDisplay();
}
}
从上面代码可以看出CameraPreviewView的核心代码是SurfaceHolder.Callback的回调:在创建/销毁时启动/停止预览动作。在CameraPreviewView类中,我们利用了View的生命周期回调来实现自动管理预览生命周期控制:
- 当SurfaceView被创建后自动开启预览
- 当SurfaceView被销毁时关闭预览
- 当SurfaceView尺寸被改变时重置预览
预览View需要注意预览输出画面的尺寸。相机输出画面只支持部分尺寸。
在启用预览View后,就可以通过Camera.takePicture()方法拍摄一张照片,返回的照片数据通过Callback接口获取。takePicture()接口可以获取三个类型的照片:
- 第一个,ShutterCallback接口,在拍摄瞬间瞬间被回调,通常用于播放“咔嚓”这样的音效;
- 第二个,PictureCallback接口,返回未经压缩的RAW类型照片;
- 第三个,PictureCallback接口,返回经过压缩的JPEG类型照片;
比如当我们要使用第三个参数时,可以通过BitmapFactory很方便地将byte数组转换为Bitmap
示例代码如下:
public abstract class BitmapCallback implements Camera.PictureCallback {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
onPictureTaken(BitmapFactory.decodeByteArray(data, 0, data.length));
}
public abstract void onPictureTaken(Bitmap bitmap);
}
6. 释放相机设备
在打开一个相机设备后,意味着你的App就独占了这个设备,其它App将无法使用它。因此在你不需要相机设备时,记得调用release()方法释放设备,再使用时可以重新打开,这并不需要多大的成本。可以选择在stopPreview()后即释放相机设备。
附加工具性代码实现
1 – 判断手机设备是否有相机设备
public static boolean hasCameraDevice(Context ctx) {
return ctx.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_CAMERA);
}
2 – 判断是否支持自动对焦
public static boolean isAutoFocusSupported(Camera.Parameters params) {
List<String> modes = params.getSupportedFocusModes();
return modes.contains(Camera.Parameters.FOCUS_MODE_AUTO);
}