上一篇文章Camera2 API 采集视频并 SurfaceView、TextureView 预览
主要是想理清 Camera2 的结构,并简单介绍怎么使用TextureView、SurfaceView 预览数据。
其实Camera2除了结构比较复杂,使用起来没那么复杂,而且是官方强烈推荐,加上又支持了很多新特性,自然满怀欣喜地准备做下去,可是,后面发现了好多坑啊!很多资料不全而且很多手机都不支持FULL模式,手头上的MIUI也是,无奈只好转战Camera。
这篇文章比较简单,主要是camera采集数据结合textureview预览,下面内容包括:
- 使用并封装Camera的api
- 使用Textureview
- 实现Camera手动对焦、自动对焦
- 源码
其中最重要的就是:Camera的坑!!! 包括
如何处理预览变形拉伸?
如何选择合适的预览尺寸?
如何textureview坐标转化到camera坐标实现手动对焦?
如何自动对焦?
Camera使用流程
1 . 申请权限
使用Camera必须要申请注册权限,包括在manifest申明和android 5.0以上必须主动申请
<uses-permission android:name = "android.permission.CAMERA" />
<uses-feature android:name = "android.hardware.camera" />
<uses-feature android:name = "android.hardware.camera.autofocus" />
以及主动申请权限
ActivityCompat.requestPermissions(Activity activity, String[] permisstions, int requestCode);
自己封装了个PermisstionUtil工具类,可见后面的源码链接
2 . 打开Camera
private static int mCameraID = Camera.CameraInfo.CAMERA_FACING_BACK;
// 默认是后置摄像头
mCamera = Camera.open(mCameraID);
3. 预览camera
1.设置camera接收预览的输出,可以是SurfaceTexture或SurfaceHolder:
mCamera.setPreviewTexture(surfaceTexture);
mCamera.setPreviewDisplay(surfaceHolder);
2.设置camera的方向,因为camera默认是水平的,不设置方向会发现看到的图像是不对的。
网上很多人写 mCamera.setDisplayOrientation(90)
,直接旋转90度,如果是固定screenOrientation=“portrait”,好像也没有问题,不过这里个人参考了其他的代码,先找出屏幕方向再进行设置:
mOrientation = getCameraPreviewOrientation(activity, mCameraID);
mCamera.setDisplayOrientation(mOrientation);
public static int getCameraPreviewOrientation(Activity activity, int cameraId) {
if (mCamera == null) {
throw new RuntimeException("mCamera is null");
}
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int result;
int degrees = getRotation(activity);
//前置
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360;
}
//后置
else {
result = (info.orientation - degrees + 360) % 360;
}
return result;
}
public static int getRotation(Activity activity) {
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
int degrees = 0;
switch (rotation) {
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;
}
return degrees;
}
4. 为camera设置预览尺寸(重点)
这是使用camera遇到的第一个坑
,预览图像拉伸变形,事实上camera提供了很多个预览尺寸,但是如何选择最合适的尺寸呢?
网上主要有两个做法,一个是直接选择与textureview宽高比最接近的预览尺寸,另外一个是选择分辨率最大的预览尺寸?
首先,第一个方案使用上发现效果不好,第二个在后置摄像头使用没问题,跟手机自带的相机效果一致,但是前置摄像头依然有拉伸感。
注意:camera获取的预览尺寸列表都是:宽 > 高,所以在获取选择合适的分辨率时候,都选择把textureview大的边设为宽,小的设为高,再进行选择比较。
这里遇到的第二个坑
,是Camera.Size的width也是大于height的,在同Size进行转换的时候需要注意
private static Camera.Size getOptimalSize(List<Camera.Size> supportList, int width, int height) {
// camera的宽度是大于高度的,这里要保证expectWidth > expectHeight
int expectWidth = Math.max(width, height);
int expectHeight = Math.min(width, height);
// 根据宽度排序(这里的宽度就是最长的那一边)
Collections.sort(supportList, new Comparator<Camera.Size>() {
@Override
public int compare(Camera.Size pre, Camera.Size after) {
if (pre.width > after.width) {
return 1;
} else if (pre.width < after.width) {
return -1;
}
return 0;
}
});
Camera.Size result = supportList.get(0);
boolean widthOrHeight = false; // 判断存在宽或高相等的Size
for (Camera.Size size: supportList) {
// 如果宽高相等,则直接返回
if (size.width == expectWidth && size.height == expectHeight) {
result = size;
break;
}
// 仅仅是宽度相等,计算高度最接近的size
if (size.width == expectWidth) {
widthOrHeight = true;
if (Math.abs(result.height - expectHeight)
> Math.abs(size.height - expectHeight)) {
result = size;
}
}
// 高度相等,则计算宽度最接近的Size
else if (size.height == expectHeight) {
widthOrHeight = true;
if (Math.abs(result.width - expectWidth)
> Math.abs(size.width - expectWidth)) {
result = size;
}
}
// 如果之前的查找不存在宽或高相等的情况,则计算宽度和高度都最接近的期望值的Size
else if (!widthOrHeight) {
if (Math.abs(result.width - expectWidth)
> Math.abs(size.width - expectWidth)
&& Math.abs(result.height - expectHeight)
> Math.abs(size.height - expectHeight)) {
result = size;
}
}
}
return result;
}
这部分代码参考自:云图网
最后整个预览的代码:
public static void startPreview(Activity activity, SurfaceTexture surfaceTexture, int width, int height) {
try {
if (mCamera != null) {
mCamera.setPreviewTexture(surfaceTexture);
Camera.Parameters parameters = mCamera.getParameters();
mOrientation = getCameraPreviewOrientation(activity, mCameraID);
Camera.Size bestPreviewSize = getOptimalSize(parameters.getSupportedPreviewSizes(), width, height);
parameters.setPreviewSize(bestPreviewSize.width, bestPreviewSize.height);
Camera.Size bestPictureSize = getOptimalSize(parameters.getSupportedPictureSizes(), width, height);
parameters.setPictureSize(bestPictureSize.width, bestPictureSize.height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
mCamera.setDisplayOrientation(mOrientation);
mCamera.startPreview();
}
} catch (IOException e) {
e.printStackTrace();
}
}
5. 打开相机、预览的时机
通过textureview. TextureView.SurfaceTextureListener的回调就可以打开、预览
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
mPreviewSize = new Size(width, height);
startPreview();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int width, int height) {
focus(width/2, height/2, true); //自动对焦
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
releasePreview();
return false;
}
Camera对焦
1. 手动对焦
camera提供了autoFocus(Camera.AutoFocusCallback)自动对焦的方法,同时我们只需要通过设置camera的parameter提供的两个对焦的参数,再调用audoFoucus就可以了:
- 设置对焦区域 parameters.setFocusAreas(focusAreas);
- 设置感光区域 parameters.setMeteringAreas(focusAreas);
看起来下一步就是如何将 textureview的点击坐标如何转换成camera对焦的坐标:
然而,网上很多提供的转换方法都是错的,这是遇到的第三个坑
首先我们来看一下预览界面坐标系统上的坐标:
以上可以看出:
view坐标 (0, 0)对应预览坐标(-1000, 1000)
view坐标 (width,0)对应预览坐标(-1000, -1000)
view坐标 (width,height)对应预览坐标(1000, -1000)
view坐标 (0, height)对应预览坐标(1000, 1000)
整理一下就可以算出对应的公式了
/**
* 将屏幕坐标转换成camera坐标
* @param screenSize
* @param focusPoint
* @return cameraPoint
*/
private static Point convertToCameraPoint(Size screenSize, Point focusPoint){
int newX = focusPoint.y * 2000/screenSize.getHeight() - 1000;
int newY = -focusPoint.x * 2000/screenSize.getWidth() + 1000;
return new Point(newX, newY);
}
整个对焦的代码就出来:
/**
* 对焦
* @param focusPoint 焦点位置
* @param screenSize 屏幕尺寸
* @param callback 对焦成功或失败的callback
* @return
*/
public static boolean newCameraFocus(Point focusPoint, Size screenSize, Camera.AutoFocusCallback callback) {
if (mCamera == null) {
throw new RuntimeException("mCamera is null");
}
Point cameraFoucusPoint = convertToCameraPoint(screenSize, focusPoint);
Rect cameraFoucusRect = convertToCameraRect(cameraFoucusPoint, 100);
Camera.Parameters parameters = mCamera.getParameters();
if (Build.VERSION.SDK_INT > 14) {
if (parameters.getMaxNumFocusAreas() <= 0) {
return focus(callback);
}
clearCameraFocus();
List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
// 100是权重
focusAreas.add(new Camera.Area(cameraFoucusRect, 100));
parameters.setFocusAreas(focusAreas);
// 设置感光区域
parameters.setMeteringAreas(focusAreas);
try {
mCamera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
return focus(callback);
}
private static boolean focus(Camera.AutoFocusCallback callback) {
if (mCamera == null) {
return false;
}
mCamera.cancelAutoFocus();
mCamera.autoFocus(callback);
return true;
}
2. 自动对焦
自动对焦是利用加速度传感器,当快速移动一段距离时候则调用手动对焦,最后效果还不错。
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
在SensorEventListener的onSensorChanged里面监控
public void onSensorChanged(SensorEvent sensorEvent) {
if (sensorEvent.sensor == null) {
return;
}
if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
int x = (int) sensorEvent.values[0];
int y = (int) sensorEvent.values[1];
int z = (int) sensorEvent.values[2];
int px = Math.abs(lastX - x);
int py = Math.abs(lastY - y);
int pz = Math.abs(lastZ - z);
lastX = x;
lastY = y;
lastZ = z;
if (px > 2.5 || py > 2.5 || pz > 2.5) {
if (mCameraSensorListener != null) {
// 发生移动,回调中调用手动对焦
mCameraSensorListener.onRock();
}
}
}
}
最后效果
源码:
后续会将textureview替换为GLSurfaceView预览
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6277.html