Android camera 使用框架(自适配前后摄像头、Preview)

由于最近要用摄像头进行图片的采集,并做人脸识别。而由于手机种类与自开发板子种类繁多,屏幕适配、前后摄像头的管理繁琐,所以本人利用几天时间,自己写了一个简单的框架,严格来说不是框架,就是个工具,使用起来更方便点。使相机支持的预览大小很好的与手机屏幕需要展示的大小做了适配,从来不会出现预览变形等问题。废话不说,直接上代码。

本工具总共主要包括以下几个类:

Android camera 使用框架(自适配前后摄像头、Preview)_第1张图片

1、CameraActivity用于演示,展示。

     CameraManager用于相机的管理,主要相机的打开、关闭等。

     CameraConfigurationManager用于相机、preview相关的参数

     CameraUtils相机相关的辅助类工具,是供上述俩类使用的。

     PreviewCallback相机拍摄到的图片数据返回接口

     CameraSurfaceView相机拍摄图像展示平台

2、接下就讲解一下各类

先上CameraManager

/**
 * 相机管理器
 */
public class CameraManager {
    private CameraConfigurationManager mConfigurationManager;
    private PreviewCallback mPreviewCallback;

    private OpenCamera mCamera;
    private boolean mInitialized ;//是否初始化
    private boolean mIsPreviewing;

    public CameraManager(Context context){
        mConfigurationManager = new CameraConfigurationManager(context);
        mPreviewCallback = new PreviewCallback(mConfigurationManager);
    }

    /**
     * 打开相机,并设置相机需要设置的参数。可以指定前后摄像头,若指定的摄像头没找到,则会打开后置摄像头,或者打开失败
     * @throws IOException
     */
    public synchronized void openCamera(CameraFacing cameraFacing) throws IOException {
        OpenCamera theCamera = mCamera;
        //没有打开过摄像头
        if (theCamera == null){
            theCamera = OpenCameraInterface.open(cameraFacing);//获得前置摄像头OpenCamera
            if (theCamera == null){
                throw new IOException("相机无法打开");
            }
            mCamera = theCamera;
        }
        Camera rawCamera = mCamera.getCamera();//得到相机
        Camera.Parameters parameters = rawCamera.getParameters();//得到相机参数
        if (!mInitialized){
            mInitialized = true;
            mConfigurationManager.initFromCamera(theCamera,parameters);//根据相机参数初始configuraion
        }
        mConfigurationManager.setCameraParameters(rawCamera);//设置相机参数
    }

    /**
     * 判断相机是否打开
     * @return
     */
    public synchronized boolean isOpen(){
        return mCamera != null;
    }

    /**
     * 关闭相机
     */
    public synchronized void closeCamera(){
        if (mCamera != null){
            mCamera.getCamera().release();
            mCamera = null;
        }
    }

    public synchronized void setPreviewDisplay(SurfaceHolder holder){
        if(mCamera!=null){
            try {
                mCamera.getCamera().setPreviewDisplay(holder);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 相机开始在surfaceView投影
     */
    public synchronized void startPreview(){
        OpenCamera theCamera = mCamera;
        if (theCamera != null && !mIsPreviewing){
            theCamera.getCamera().startPreview();
            mIsPreviewing = true;
            //TODO :detectFaceOneTime autoFocus
        }
    }

    /**
     * 相机停止投影
     */
    public synchronized void stopPreview(){
        if (mCamera != null && mIsPreviewing){
//            mCamera.getCamera().setOneShotPreviewCallback(null); //若回调一次预览数据,则可以关闭
            mCamera.getCamera().stopPreview();
            mIsPreviewing = false;
        }
    }

    /**
     * 请求画面框架
     * 回调一次预览帧数据
     */
    public synchronized void requestPreviewFrame(){
        OpenCamera theCamera = mCamera;
        if(theCamera != null && mIsPreviewing){
//            theCamera.getCamera().setOneShotPreviewCallback(mPreviewCallback);//回调一次预览帧数据
            theCamera.getCamera().setPreviewCallback(mPreviewCallback);
        }
    }

    public CameraConfigurationManager getConfigurationManager() {
        return mConfigurationManager;
    }
}

该类主要提供打开相机、关闭相机。而为什么设置setPreviewDisplay,是由于,该方法需要在打开相机后,才可以知道相机的Preview参数,这样才能对应调整展示控件CameraSurfaceView的大小。然后才能调用这个方法设置相机的展示平台。


接下来就是核心CameraConfiguraionManager,他相当于计算中心,通过屏幕尺寸、相机预览尺寸,来得到最佳相机预览尺寸与屏幕展示比例,这样可以使相机预览界面更适合屏幕大小、方向等。代码如下:

/**
 * 通过相机与手机屏幕参数,初始化参数
 */
public class CameraConfigurationManager {
    private static final String TAG = "ConfigurationManager";
    private static final int MAX_SIZE_OF_MIN_EDGE = 500;
    private Context mContext;
    private int mRotationFromDisplayToCamera;// set the camera direction according to the screen direction
    private int mNeedRotation;
    private Point mScreenResolution;// the screen resolution
    private Point mCameraResolution;// the camera resolution according to mBestPreviewSize
    private Point mBestPreviewSize;//the calculated preview size the most suitable size
    private Point mPreviewSizeOnScreen;// final preview size according to screen orientaion and mBestPreviewSize
    private int mPreviewFormat;// preview camera format
    private float mPreviewToScreenRatio;// the ratio of preview size to sreen size
    private float mCalcScaleRatio ; //mBestPreviewSize minimum side /  500

    public CameraConfigurationManager(Context context) {
        mContext = context;
    }

      /**
     * init preview size、preview format and ratio
     * @param camera
     * @param parameters
     */
    public void initFromCamera(OpenCamera camera, Camera.Parameters parameters) {
        WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        int displayRotation = display.getRotation();//得到手机屏幕旋转方向,返回的值,正是用户需要补偿的方向, 竖屏为0,横屏为1,竖屏 高比宽大
        int rotationToDisplay ;
        switch (displayRotation){//屏幕旋转是逆时针方向
            case Surface.ROTATION_0:
                rotationToDisplay = 0;
                break;
            case Surface.ROTATION_90:
                rotationToDisplay = 90;
                break;
            case Surface.ROTATION_180://这个值不可能出现
                rotationToDisplay = 180;
                break;
            case Surface.ROTATION_270:
                rotationToDisplay = 270;
                break;
            default:
                if (displayRotation % 90 == 0){//是90度的整数倍
                    rotationToDisplay = (360+displayRotation) %360;
                }else {
                    throw new IllegalArgumentException("rotation: " + displayRotation);
                }
        }
        int rotationToCamera = camera.getOrientation();//得到相机的方向,这个值只影响拍照的返回图片方法,对于摄像不起作用
        if (camera.getCameraFacing() == CameraFacing.FRONT){//前置摄像头,需经过处理。例如:后置摄像头是90,那前置摄像头就是270
            rotationToCamera = (360-rotationToCamera) %360;
        }

        mRotationFromDisplayToCamera = (360 + rotationToCamera - rotationToDisplay) % 360;//????

        if (camera.getCameraFacing() == CameraFacing.FRONT){
            mNeedRotation = (360-mRotationFromDisplayToCamera) %360;
        }else {
            mNeedRotation = mRotationFromDisplayToCamera;
        }

        Point theScreenResolution = new Point();
        display.getSize(theScreenResolution);//得到屏幕的尺寸,单位是像素
        mScreenResolution = theScreenResolution;

        mBestPreviewSize = CameraUtils.findBestPreviewSizeValue(parameters,theScreenResolution);//通过相机尺寸、屏幕尺寸来得到最好的展示尺寸,此尺寸为相机的
        mCameraResolution = new Point(mBestPreviewSize);

        boolean isScreenPortrait = mScreenResolution.x < mScreenResolution.y;
        boolean isPreviewSizePortrait = mBestPreviewSize.x < mBestPreviewSize.y;
        if (isScreenPortrait == isPreviewSizePortrait){//相机与屏幕一个方向,则使用相机尺寸
            mPreviewSizeOnScreen = mBestPreviewSize;
        }else {
            mPreviewSizeOnScreen = new Point(mBestPreviewSize.y,mBestPreviewSize.x);//否则翻个
        }
        mPreviewFormat = CameraUtils.findAvailablePreviewFormat(parameters, ImageFormat.NV21, ImageFormat.YUY2);//查询相机是否支持ImageFormat.NV21或者ImageFormat.YUY2格式
        mPreviewToScreenRatio = (float)mPreviewSizeOnScreen.x/mScreenResolution.x;//相机预览大小与屏幕大小的比例
        mCalcScaleRatio = Math.min(mBestPreviewSize.x,mBestPreviewSize.y)/MAX_SIZE_OF_MIN_EDGE;
        if(mCalcScaleRatio==0){
            mCalcScaleRatio=1;
        }
    }

    /**
     * camera set previewSize、previewFormat、cameraParamter
     * @param camera
     */
    public void setCameraParameters(Camera camera) {
        Camera.Parameters parameters = camera.getParameters();
        parameters.setPreviewSize(mBestPreviewSize.x,mBestPreviewSize.y);
        if (mPreviewFormat != -1){
            parameters.setPreviewFormat(mPreviewFormat);//设置相机预览格式
        }
        CameraUtils.setFocus(parameters,true,false,true);//设置相机对焦模式
        CameraUtils.setBarcodeSceneMode(parameters,Camera.Parameters.SCENE_MODE_BARCODE);//设置相机场景模式
        CameraUtils.setBestPreviewFPS(parameters);//设置相机帧数
        CameraUtils.setAntiBanding(parameters);//设置防牛顿环配置 太专业不太懂。哈哈
        camera.setParameters(parameters);
        camera.setDisplayOrientation(mRotationFromDisplayToCamera);//设置预览方向,可以让本身横屏的相机展示出来竖屏,正方向的肖像
        //reset mBestPreviewSize according to afterParameter,prevent the above setting from invalidation 为了防止上述previewSzie设置失效,因为有可能设置的值,相机不支持。
        Camera.Parameters afterParameter = camera.getParameters();
        Camera.Size afterSize = afterParameter.getPreviewSize();
        if (afterSize != null && (mBestPreviewSize.x != afterSize.width
                                || mBestPreviewSize.y != afterSize.height)){
            mBestPreviewSize.x = afterSize.width;
            mBestPreviewSize.y = afterSize.height;
        }
    }

    public int getRotationFromDisplayToCamera() {
        return mRotationFromDisplayToCamera;
    }

    public int getNeedRotation() {
        return mNeedRotation;
    }

    public Point getScreenResolution() {
        return mScreenResolution;
    }

    public Point getBestPreviewSize() {
        return mBestPreviewSize;
    }

    public Point getPreviewSizeOnScreen() {
        return mPreviewSizeOnScreen;
    }

    public int getPreviewFormat() {
        return mPreviewFormat;
    }

    public float getPreviewToScreenRatio() {
        return mPreviewToScreenRatio;
    }

    public float getCalcScaleRatio(){
        return mCalcScaleRatio;
    }

    public Point getCameraResolution() {
        return mCameraResolution;
    }
}


最后有一个根据控件最大展示尺寸与相机预览尺寸大小来计算出控件所设大小,代码如下,在CameraUtils类中。

    /**
     * 根据相机预览尺寸、控件可展示最大尺寸来计算控件的展示尺寸,防止图像变形
     * @param previewSizeOnScreen
     * @param maxSizeOnView
     * @return
     */
    public static Point calculateViewSize(Point previewSizeOnScreen,Point maxSizeOnView){
        Point point=new Point();
        float ratioPreview=(float) previewSizeOnScreen.x/(float)previewSizeOnScreen.y;//相机预览比率
        float ratioMaxView=(float) maxSizeOnView.x/(float)maxSizeOnView.y;//控件比率
        if(ratioPreview>ratioMaxView){//x>y,以控件宽为标准,缩放高
            point.x=maxSizeOnView.x;
            point.y= (int) (maxSizeOnView.x/ratioPreview);
        }else{
            point.y=maxSizeOnView.y;
            point.x= (int) (maxSizeOnView.y*ratioPreview);
        }
        return point;
    }

最后来一个调用activity

activity的布局如下:



    

代码如下

public class CameraActivity extends Activity {
    private final static String TAG="CameraActivity";

    private FrameLayout camera_preview;
    private CameraSurfaceView cameraSurfaceView;
    private CameraManager mCameraManager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        camera_preview = (FrameLayout) findViewById(R.id.camera_preview);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.e(TAG,"onResume...");
        if(CameraUtils.checkCameraHardware(this)){
            openCamera();//需要在子线程中操作
            relayout();
            cameraSurfaceView.onResume();
        }else{
            Toast.makeText(this,"该手机不支持摄像头!",Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.e(TAG,"onPause...");
        cameraSurfaceView.onPause();
    }

    /**
     * 初始化相机,主要是打开相机,并设置相机的相关参数,并将holder设置为相机的展示平台
     */
    private void openCamera() {
        mCameraManager = new CameraManager(this);
        if (mCameraManager.isOpen()) {
            Log.w(TAG, "surfaceCreated: 相机已经被打开了");
            return;
        }
        try {
            mCameraManager.openCamera(CameraFacing.FRONT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    /**
     * 设置界面展示大小
     */
    private void relayout() {
        // Create our Preview view and set it as the content of our activity.
        cameraSurfaceView = new CameraSurfaceView(this,mCameraManager);
        Point previewSizeOnScreen = mCameraManager.getConfigurationManager().getPreviewSizeOnScreen();//相机预览尺寸
        Point screentPoint=mCameraManager.getConfigurationManager().getScreenResolution();//自己展示相机预览控件所能设置最大值
        Point point = CameraUtils.calculateViewSize(previewSizeOnScreen, screentPoint);
        FrameLayout.LayoutParams layoutParams=new FrameLayout.LayoutParams(point.x,point.y);
        layoutParams.gravity= Gravity.CENTER;
        cameraSurfaceView.setLayoutParams(layoutParams);
        camera_preview.addView(cameraSurfaceView);
    }
}


注意,由于openCamera比较耗时,建议放在子线程中进行操作。国供师是这么说的。

有什么不对,希望积极指导,谢谢!


CSDN下载地址:点击打开链接

github下载地址:点击打开链接

喜欢的话就点个赞,不对的地方请多多指导。

你可能感兴趣的:(android)