谷歌推荐开发者不使用Camera1 API,使用Camera2作为相机开发的接口。但是我在一些老的项目中遇到Camera1,且还需在其基础上进行扩展。
出于好奇心,且抱着一种学习的态度,我还是总结了一些Camera1的知识。
本文主要介绍,打开相机后,如何设置相机相关的参数,如预览方向、照片方向等。在自己开发的过程中,这一部分耗时最多,因此,记录下来以便以后温习和使用。
当手机竖直时为默认的设备方向,此时设备方向为0°;
若手机顺时针分别旋转90、180、270,此时的设备方向依次为90°、180°、270°。
当手机由默认方向顺时针旋转270°成横屏,后置相机的顶端在设备的右边缘,此时的状态即为相机的默认状态,方向为0°;
相机默认方向与设备默认方向间的夹角为90°。
设备竖直时为默认的方向,旋转角度为0°;
可以通过如下方法获取设备当前方向与默认方向间的夹角。
WindowManager wm = (WindowManager) ComponentContext.getContext()
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int result = 0;
try {
Method m = display.getClass().getDeclaredMethod("getRotation");
switch ((Integer) m.invoke(display)) {
case Surface.ROTATION_0:
result = 0;
break;
case Surface.ROTATION_90:
result = 90;
break;
case Surface.ROTATION_180:
result = 180;
break;
case Surface.ROTATION_270:
result = 270;
break;
default:
break;
}
} catch (Exception e) {
return result;
}
return result;
为了让相机预览画面与我们拿着手机时的朝向一致,需要将预览旋转一个角度;
通过如下方法获取这个角度;
int degrees = 0;
switch (screenRotation) {
case Surface.ROTATION_0:
degrees = 0;
break;
case Surface.ROTATION_90:
degrees = 90;
break;
case Surface.ROTATION_180:
degrees = 180;
break;
case Surface.ROTATION_270:
degrees = 270;
break;
}
int result = 0;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else {// back-facing
result = (info.orientation - degrees + 360) % 360;
}
return result;
screenRotation是在1.3中获取的设备方向,info是与当前相机相关的CameraInfo对象,其获取方式如下:
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
最后返回的结果result,将赋值给Camera,从而设置预览方向。
mCamera.setDisplayOrientation(result);
为了让拍出来的照片与我们手持设备预览时看到的效果一致,我们需要设置,让照片旋转一个角度。
通过如下方法获取照片待设置的方向:
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
return (info.orientation - camRotation + 360) % 360;
} else {
return (info.orientation + camRotation) % 360;
}
其中info为上述所述的CameraInfo对象;camRotation即为1.3中获得的设备方向。
得到了结果后,将其设置给相机:
Camera.Parameters parameters = mCamera.getParameters();
//.....
parameters.setRotation(pictureRotation);
通过如下方式即可获得:
List supportedPreviewSizes = parameters.getSupportedPreviewSizes();
通过debug发现,该List中的每个Size,其width都是大于height的。这也正好印证了,相机的默认方向是横屏的。
这里直接提供方法,并在注释中解释了方法的大致逻辑:
/**
* 选取与width、height比例最接近的、设置支持的size
* @param context
* @param sizes 设置支持的size序列
* @param w 相机预览视图的width
* @param h 相机预览视图的height
* @return
*/
public static Camera.Size getOptimalSize(Context context, List sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;//阈值,用于选取最优
double targetRatio = -1;
int orientation = context.getResources().getConfiguration().orientation;
//保证targetRatio始终大于1,因为size.width/size.height始终大于1
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
targetRatio = (double) h / w;
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
targetRatio = (double) w / h;
}
Camera.Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = Math.min(w, h);
for (Camera.Size size : sizes) {
double ratio = (double) size.width / size.height;
//若大于了阈值,则继续筛选
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
continue;
}
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
//若通过比例没有获得最优,则通过最小差值获取最优,保证至少能得到值
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Camera.Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
parameters.setPreviewSize(optimizePreviewSize.width, optimizePreviewSize.height);
需要注意的是,预览view的宽高必须和设备支持的预览宽高相近或相等,否则会造成过度的拉伸、缩放;
如果view宽高很大,而预览尺寸很小,分辨率很低,预览质量会不好。
设置参数时,必须按照默认相机的方向,以Size.width和Size.height的秩序赋予数值。
首先仍是获得设备支持的照片尺寸序列:
List supportedPictureSizes = parameters.getSupportedPictureSizes();
然后通过#4.2中的方法,获得最佳的照片尺寸;
最后设置到参数中:
parameters.setPictureSize(optimizePictureSize.width, optimizePictureSize.height);
首先获取设备支持的对焦模式:
List supportedFocusMode = parameters.getSupportedFocusModes();
遍历这些对焦模式,设置需要的模式:
for (String mode : supportedFocusMode) {
if (mode.equals(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(mode);
break;
}
}
综上几步,完成了参数的设置;
当然,若要实现更多的功能,还需要进行更多的设置。
当你的参数设置完成后,需要调用如下方法,将参数写给相机:
mCamera.setParameters(parameters);
若参数有设置不正确,或设备不支持该参数,会报出set parameter failed的提示。
拍照后,通过回调得到byte[]数组,存放着拍摄照片的字节流数据data;
此时,还不能直接存储照片,还需要做进一步的处理。
Matrix m = new Matrix();
if (mPreview.isFrontFace()) {//前置摄像头要水平翻转
m.postScale(-1, 1);
}
String manufacturer = Build.MANUFACTURER;
if (manufacturer != null && (manufacturer.equalsIgnoreCase("xiaomi")
|| manufacturer.equals("samsung")
|| manufacturer.equals("sony"))) {//部分机型进行照片方向适配
m.postRotate(90);
}
String path = "xxx";
Matrix m = new Matrix();
if (mPreview.isFrontFace()) {//前置摄像头要水平翻转
m.postScale(-1, 1);
}
String manufacturer = Build.MANUFACTURER;
if (manufacturer != null && (manufacturer.equalsIgnoreCase("xiaomi")
|| manufacturer.equals("samsung")
|| manufacturer.equals("sony"))) {//部分机型进行照片方向适配
m.postRotate(90);
}
if (!m.isIdentity()) {//若设置了矩阵,要使用矩阵
Bitmap tmp = ImageHelper.byteArray2Bitmap(data);
Bitmap revisedBmp = Bitmap.createBitmap(tmp, 0, 0, tmp.getWidth(), tmp.getHeight(), m, true);
data = ImageHelper.bitmap2ByteArray(revisedBmp);
}
boolean isSuccess = ImageHelper.saveJPGToSdcard(path, data);
上述即为我整理的Camera1参数设置相关笔记,若有不正确的地方,希望大家多沟通交流。