上一篇博客Android Camera开发(一)之基础知识已经把Camera开发的主要流程梳理了一遍,包括调用系统Camera和自定义自己的Camera,如果你还没有阅读,请移步至链接吧。本文主要是Camera开发的一些遗留问题,包括如何设置Camera的特性参数,让它更好的辅助相机拍照,以及对照片和视频的旋转处理。
一旦你成功访问相机设备,你可以使用camera.getParameters()方法来获取相机参数信息,可以根据返回值 Camera.Parameters 类来查看当前camera支持哪些参数设置等。当使用API 9或者更高时,你可以使用Camera.getCameraInfo()静态方法来获取前后camera的信息,如camera数据流的方向和是否能禁止拍照快门声音标记。示例代码如下:
public static Camera.CameraInfo getCameraInfo(int cameraId) {
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, cameraInfo);
return cameraInfo;
}
CameraInfo类:
public static class CameraInfo {
public static final int CAMERA_FACING_BACK = 0; //后置camera
public static final int CAMERA_FACING_FRONT = 1; //前置camera
public boolean canDisableShutterSound; //是否能禁止拍照快门声音
public int facing;
public int orientation; //camera数据流的方向
}
}
Android支持一组相机功能来控制你的相机应用,比如:生成的照片格式,闪光灯模式,对焦设置等等。这里罗列出通用的相机功能并且显示怎么使用它们来辅助我们拍照。有关相机更多的参数设置,可以直接阅读Camera.Parameters类。下表罗列出通用的相机参数:
注意:以上列表中的功能并是不在所有的Android设备上都支持,因此你在使用以上参数时需要去检测当前Android设备是否支持该参数,然后再去使用它们来辅助相机拍照。
首先你要知道,并不是所有android设备都支持全部的camera特性功能,因此在应用总使用camera特性功能需要先检测是否支持,然后在去使用。否则你使用了不支持的camera特性功能将会报错。
在应用中可以通过得到camera 的参数parameters类,然后通过该类中的一些方法来检测当前设备是否支持camea特性功能。如下代码示例演示了如何获得一个Camera.Parameters对象且检测camera是否支持自动对焦特性:
Camera.Parameters params = mCamera.getParameters();
List focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
/** Autofocus mode is supported */
}
大部分Android 相机特性功能都可以通过 Camera.Parameters类来控制。首先你可以获得一个Camera实例,然后调用camera.getParameters()方法的返回值来得到Camera.Parameters实例,之后就可以通过Parameters.setxxx()系列方法来设置一些参数使用相机的一些特性功能。以下是实例代码:
Camera.Parameters params = mCamera.getParameters();
/** set the focus mode*/
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);
相机的所有参数都可以通过类似以上方法来设置,一般在打开相机成功以后就可以设置相机的基本参数。
注意:相机的一些特性不能在任意时刻改变,比如改变预览的尺寸和方向时,首先需要停止preview,修改预览尺寸之后再次重启preview。自从Android4.0以后修改预览方向以后无需 再次重启preview。
下面介绍三个相机的其他功能的使用:
在某些摄像情景中,自动调焦和测光可能不能达到设计结果。从Android4.0(API Level 14)开始,你的Camera应用程序能够提供另外的控制允许应用程序或用户指定图像中特定区域用于进行调焦或光线级别的设置,并且把这些值传递给Camera硬件用于采集图片或视频。
测光和调焦区域的工作与其他Camera功能非常类似,你可以通过Camera.Parameters对象中的方法来控制它们。下列代码演示如何给Camera示例设置两个测光区域:
private void setMeteringFocusAreas(Camera camera) {
Camera.Parameters params = camera.getParameters();
if (params.getMaxNumMeteringAreas() > 0){ //检测是否支持测光和对焦区域功能
List meteringAreas = new ArrayList();
Rect areaRect1 = new Rect(-100, -100, 100, 100); //定义图像中间的一个区域
meteringAreas.add(new Camera.Area(areaRect1, 600)); //权重60%
Rect areaRect2 = new Rect(800, -1000, 1000, -800); //定义图像右上侧的一个区域
meteringAreas.add(new Camera.Area(areaRect2, 400)); //权重40%
params.setMeteringAreas(meteringAreas);
}
camera.setParameters(params);
}
Camera.Area对象包含了两个数据参数:Rect对象,它用于指定Camera预览窗口一块矩形区域;一个权重值:它告诉Camera这块指定区域应该给予的测光或调焦计算的重要性等级。
Camera预览窗口被映射成2000x2000单元格的矩形。坐标(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角,如下图所示:
图中的红线说明了Camera预览窗口中给Camera.Area指定的坐标系统。用Rect的值是(-100, -100, 100, 100)和(800, -1000, 1000, -800)蓝色框代表了需要设置的测光或调焦区域。
对于包含人的图片,通常人脸是图片的最重要的部分,并且在采集图像时,应该使用调焦和白平衡来进行检测。Android4.0(API Level 14)框架提供了用于识别人脸和使用人脸识别技术来计算图片设置的API。
注意:当人脸识别在运行时,setWhiteBalance(String), setFocusAreas(List) 和setMeteringAreas(List)方法都无效。
在你的应用中使用人脸识别技术一般需要如下几步:
人脸识别功能并不是在所有设备上都支持。你应当调用getMaxNumDetectedFaces()方法来检测当前设备是否支持人脸识别技术,只有当以上方法返回值大于0时,你才可以去调用startFaceDetection()方法去启动人脸识别。
为了通知和响应人脸识别,你的相机应用必须设置一个人脸检测监听事件,为了到达这个目的,你必须要创建一个实现Camera.FaceDetectionListener接口的监听器类,如下所示:
class MyFaceDetectionListener implements Camera.FaceDetectionListener {
@Override
public void onFaceDetection(Face[] faces, Camera camera) {
if (faces.length > 0){
showlog("face detected: "+ faces.length + " Face 1 Location X: " + faces[0].rect.centerX() + "Y: " + faces[0].rect.centerY() );
}
}
}
创建这个类之后,把它设置给你的应用程序的Camera对象:
camera.setFaceDetectionListener(new MyFaceDetectionListener());
你的应用必须在每次重启相机preview时候去启动一个人脸检测。创建一个启动人脸识别的方法,在你需要的时候调用它。示例代码如下:
public void startFaceDetection(){
Camera.Parameters params = camera.getParameters();
if (params.getMaxNumDetectedFaces() > 0){
camera.startFaceDetection();
}
}
你必须在每次启动Camera预览窗口时都要启动面部识别。
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera = getCamera();
camera.setFaceDetectionListener(new MyFaceDetectionListener()); //添加监听回调
camera.setPreviewDisplay(holder);
camera.setDisplayOrientation(90);
camera.startPreview();
startFaceDetection(); //开始检测
} catch (Exception e) {
finish();
}
}
延时摄影允许用户把几张图片合成为一个几秒或几分钟的视频剪辑。这个功能要使用MediaRecorder对象来记录图像的延时序列。
要用MediaRecorder对象来记录延时视频,和录制普通视频一样,必须要配置MediaRecorder对象,并把每秒采集的帧数设置到较小的数字,并且要使用一个延时品质设置,如下代码所示:
...
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH)); /**QUALITY_TIME_LAPSE_HIGH*/
mediaRecorder.setCaptureRate(0.1); /**每10秒抓取一帧*/
这些设置是要对MediaRecorder对象所要做的必要设置的一大部分。对于完全的配置代码示例,请看上篇博客内容。一旦配置完成,你就可以把它当做普通的视频剪辑来录制视频了。
使用上篇文章的demo,拍摄照片,如果你找到拍摄后的照片,会发现照片是横向的,也就是有90度的旋转,那么如何解决这个问题呢?还有,如果我们想不管相机在拍照时的旋转角度如何,都希望拍出来的照片是正向的,又该怎么办呢?
我的解决思路是在Camera.PictureCallback中将图片的byte[]数据转化为Bitmap,然后根据拍摄角度将Bitmap进行相应的矩阵旋转操作,最后将旋转后的Bitmap再次转化为byte[]数组。这样不管你相机的拍摄角度如何,拍出来的照片都是正向的。
private OrientationEventListener mOrientationListener;
private int orientations;
...
@Override
protected void onResume() {
super.onResume();
mOrientationListener = new OrientationEventListener(this){
@Override
public void onOrientationChanged(int orientation) {
orientations = orientation;
}
};
if(mOrientationListener != null){
mOrientationListener.enable();
}
@Override
protected void onStop() {
super.onStop();
if(mOrientationListener != null){
mOrientationListener.disable();
}
}
//拍摄照片后的回调
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
File pictureFile = MainActivity.getOutputMediaFile(MEDIA_TYPE_IMAGE);
if (pictureFile == null){
return;
}
/***********************解决照片旋转问题start************************/
if (null != data && data.length > 0) {
// data[]转为bitmap
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
int h1 = bitmap.getHeight();
int w1 = bitmap.getWidth();
Matrix matrix = new Matrix();
int initialAngle = 0;
if (h1 < w1) {
initialAngle = 90;
}
showlog("orientations : " + orientations);
if (orientations > 325 || orientations <= 45) {
matrix.setRotate(0 + initialAngle);
} else if (orientations > 45 && orientations <= 135) {
matrix.setRotate(90 + initialAngle);
} else if (orientations > 135 && orientations < 225) {
matrix.setRotate(180 + initialAngle);
} else {
matrix.setRotate(270 + initialAngle);
}
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
data = Bitmap2Bytes(bitmap);
}
/***********************解决照片旋转问题end************************/
try {
FileOutputStream fos = new FileOutputStream(pictureFile);
fos.write(data);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(RecordVedioAct.this, "图像已保存", Toast.LENGTH_SHORT).show();
camera.startPreview(); //拍完继续预览
}
};
public byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
同样,如果我们想不管相机在拍摄时的旋转角度如何,都希望拍出来的视频是正向的,怎么办呢?可以参考上面拍照时的思路。
拍摄时先获取相机角度turnAngle,初始化MediaRecorder时,将MediaRecorder通过函数mediaRecorder.setOrientationHint(turnAngle);旋转角度turnAngle,再进行拍摄。
// 开始录像
private void startRecord() {
/***********************解决视频旋转问题start************************/
int turnAngle = 0;
showlog("orientations : " + orientations);
if (orientations > 325 || orientations <= 45) {
turnAngle = 90;
} else if (orientations > 45 && orientations <= 135) {
turnAngle = 180;
} else if (orientations > 135 && orientations < 225) {
turnAngle = 270;
} else {
turnAngle = 0;
}
/***********************解决视频旋转问题end************************/
if (prepareVideoRecorder(turnAngle)) {
mediaRecorder.start();
isRecording = true;
Toast.makeText(RecordVedioAct.this, "开始录像", Toast.LENGTH_SHORT).show();
btn_start_recording.setBackgroundResource(R.drawable.recording_act_vedio_stop);
start_time = 0;
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
start_time++;
handler.sendEmptyMessage(0);
}
}, 0, 1000);
} else {
mediaRecorder.release();
camera.lock();
}
}
//初始化MediaRecorder
private boolean prepareVideoRecorder(int turnAngle){
mediaRecorder = new MediaRecorder();
camera.unlock();
mediaRecorder.setCamera(camera);
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));
mediaRecorder.setOutputFile(MainActivity.getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());
mediaRecorder.setPreviewDisplay(myHolder.getSurface());
mediaRecorder.setOrientationHint(turnAngle); //mediaRecorder旋转角度turnAngle
try {
mediaRecorder.prepare();
} catch (Exception e) {
mediaRecorder.release();
camera.lock();
return false;
}
return true;
}