1. 程式人生 > >Android camera 使用框架(自適配前後攝像頭、Preview)

Android camera 使用框架(自適配前後攝像頭、Preview)

由於最近要用攝像頭進行圖片的採集,並做人臉識別。而由於手機種類與自開發板子種類繁多,螢幕適配、前後攝像頭的管理繁瑣,所以本人利用幾天時間,自己寫了一個簡單的框架,嚴格來說不是框架,就是個工具,使用起來更方便點。使相機支援的預覽大小很好的與手機螢幕需要展示的大小做了適配,從來不會出現預覽變形等問題。廢話不說,直接上程式碼。

本工具總共主要包括以下幾個類:


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的佈局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#fa0410"
    >
    <FrameLayout
        android:id="@+id/camera_preview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"

        />
</LinearLayout>

程式碼如下
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下載地址:點選開啟連結

喜歡的話就點個贊,不對的地方請多多指導。