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以上必须主动申请

<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/tech/app/6277.html

(0)
上一篇 2021年7月17日 00:45
下一篇 2021年7月17日 00:45

相关推荐

发表回复

登录后才能评论