Camera 采集数据通过 textureview 预览,手动对焦、自动对焦 (一)

上一篇文章Camera2 API 采集视频并 SurfaceView、TextureView 预览
主要是想理清 Camera2 的结构,并简单介绍怎么使用TextureView、SurfaceView 预览数据。

其实Camera2除了结构比较复杂,使用起来没那么复杂,而且是官方强烈推荐,加上又支持了很多新特性,自然满怀欣喜地准备做下去,可是,后面发现了好多坑啊!很多资料不全而且很多手机都不支持FULL模式,手头上的MIUI也是,无奈只好转战Camera。

这篇文章比较简单,主要是camera采集数据结合textureview预览,下面内容包括:

  1. 使用并封装Camera的api
  2. 使用Textureview
  3. 实现Camera手动对焦、自动对焦
  4. 源码

其中最重要的就是:Camera的坑!!! 包括
如何处理预览变形拉伸?
如何选择合适的预览尺寸?
如何textureview坐标转化到camera坐标实现手动对焦?
如何自动对焦?

Camera使用流程

1 . 申请权限

使用Camera必须要申请注册权限,包括在manifest申明和android 5.0以上必须主动申请




以及主动申请权限

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 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() {
            @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;
    }

这部分代码参考自:https://www.jianshu.com/p/9e0f3fc5a3b4

最后整个预览的代码:

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对焦的坐标:
然而,网上很多提供的转换方法都是错的,这是遇到的第三个坑

首先我们来看一下预览界面坐标系统上的坐标:

Camera 采集数据通过 textureview 预览,手动对焦、自动对焦 (一)_第1张图片

以上可以看出:

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 focusAreas = new ArrayList();
            // 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();
            }
        }
    }
}

最后效果
效果图

源码:

https://github.com/ChyengJason/TexturePreviewCamera

后续会将textureview替换为GLSurfaceView预览

参考:
https://www.jianshu.com/p/9e0f3fc5a3b4
https://www.jianshu.com/p/39a015f2996e
https://blog.csdn.net/u013560890/article/details/49338031

你可能感兴趣的:(android,音视频)