Android Studio 呼叫Camera實現拍照功能

雪飄碧鴛發表於2018-08-30

最近總想寫寫,但又不知寫些什麼,想了想,今天寫個Camera拍照的教程吧。本例子的流程為   首先通過SurfaceView將Camera的實時畫面顯示在螢幕上,然後通過點選拍照對當前畫面進行捕捉,最後將獲得的圖片儲存至本地。

  1. 首先建立一個SurfaceHolder實現對SurfaceView的回撥,然後重寫SurfaceCreate函式,實現對Camera的初始化等一系列工作:程式碼如下:
    @Override
            public void surfaceCreated(SurfaceHolder holder) {
                Log.e("TAG","------surfaceCreated------");
                try {
                    //這裡我優先找後置攝像頭,找不到再找前面的
                    int cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
                    if (cameraIndex == -1) {
                        cameraIndex = findBackOrFrontCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
                        if (cameraIndex == -1) {
                            Log.e("TAG", "No Camera!");
                            currentCameraType = CAMERA_NOTEXIST;
                            currentCameraIndex = -1;
                            return;
                        } else {
                            currentCameraType = FRONT;
                        }
                    } else {
                        currentCameraType = BACK;
                    }
    
                    //找到想要的攝像頭後,就開啟
                    if (mCamera == null) {
                        mCamera = openCamera(currentCameraType);
                    }
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    

    本例子中,我首先找到想要開啟的攝像頭,這裡的優先尋找後置攝像頭,如果沒有找到,再找前置的,程式碼如下:

        /**
         * 按要求查詢攝像頭
         *
         * @param camera_facing 按要求查詢,鏡頭是前還是後
         * @return -1表示找不到
         */
        private int findBackOrFrontCamera(int camera_facing) {
            int cameraCount = 0;
            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
            cameraCount = Camera.getNumberOfCameras();
            for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
                Camera.getCameraInfo(camIdx, cameraInfo);
                if (cameraInfo.facing == camera_facing) {
                    return camIdx;
                }
            }
            return -1;
        }
    

    當找到攝像頭後,便開啟Camera,其實開啟Camera可以直接用open(CameraId)函式即可,但我在重新封裝了一下,直接帖程式碼:

    
        /**
         * 按照type的型別開啟相應的攝像頭
         *
         * @param type 標誌當前開啟前還是後的攝像頭
         * @return 返回當前開啟攝像機的物件
         */
        private Camera openCamera(int type) {
            int frontIndex = -1;
            int backIndex = -1;
            int cameraCount = Camera.getNumberOfCameras();
    
            Camera.CameraInfo info = new Camera.CameraInfo();
            for (int cameraIndex = 0; cameraIndex < cameraCount; cameraIndex++) {
                Camera.getCameraInfo(cameraIndex, info);
                if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                    frontIndex = cameraIndex;
                } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    backIndex = cameraIndex;
                }
            }
    
            currentCameraType = type;
            if (type == FRONT && frontIndex != -1) {
                currentCameraIndex = frontIndex;
                return Camera.open(frontIndex);
            } else if (type == BACK && backIndex != -1) {
                currentCameraIndex = backIndex;
                return Camera.open(backIndex);
            }
            return null;
        }

     

  2. 然後在SurfaceChange對Camera進行一系列初始化(對攝像頭初始化,就是設定圖片格式,圖片尺寸等賦值,要開啟攝像頭才可以初始化,否則會報錯)
        /**
         * 初始化攝像頭
         * @param holder
         */
        private void initCamera(SurfaceHolder holder){
            Log.e("TAG","initCamera");
            if (mPreviewRunning)
                mCamera.stopPreview();
    
            Camera.Parameters parameters;
            try{
                //獲取預覽的各種解析度
                parameters = mCamera.getParameters();
            }catch (Exception e){
                e.printStackTrace();
                return;
            }
            //這裡我設為480*800的尺寸
            parameters.setPreviewSize(480,800);
            // 設定照片格式
            parameters.setPictureFormat(PixelFormat.JPEG);
            //設定圖片預覽的格式
            parameters.setPreviewFormat(PixelFormat.YCbCr_420_SP);
            setCameraDisplayOrientation(this,currentCameraIndex,mCamera);
            try{
                mCamera.setPreviewDisplay(holder);
            }catch(Exception e){
                if(mCamera != null){
                    mCamera.release();
                    mCamera = null;
                }
                e.printStackTrace();
            }
            mCamera.startPreview();
            mPreviewRunning = true;
        }
    
        /**
         * 設定旋轉角度
         * @param activity
         * @param cameraId
         * @param camera
         */
        private void setCameraDisplayOrientation(Activity activity,int cameraId,Camera camera){
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(cameraId,info);
            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;
            }
            int result;
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT){
                result = (info.orientation + degrees) % 360;
                result = (360 - result) % 360;
            }else{
                result = (info.orientation - degrees +360) % 360;
            }
            camera.setDisplayOrientation(result);
        }
    
     
  3. 當這些工作完成後,便可以對當前攝像頭捕捉的畫面進行拍照了:
        /**
         * 實現拍照功能
         */
        public void takePhoto(){
            Camera.Parameters parameters;
            try{
                parameters = mCamera.getParameters();
            }catch(Exception e){
                e.printStackTrace();
                return;
            }
            //獲取攝像頭支援的各種解析度,因為攝像頭陣列不確定是按降序還是升序,這裡的邏輯有時不是很好找得到相應的尺寸
            //可先確定是按升還是降序排列,再進對對比吧,我這裡攏統地找了個,是個不精確的...
            List<Camera.Size> list = parameters.getSupportedPictureSizes();
            int size = 0;
            for (int i =0 ;i < list.size() - 1;i++){
                if (list.get(i).width >= 480){
                    //完美匹配
                    size = i;
                    break;
                }
                else{
                    //找不到就找個最接近的吧
                    size = i;
                }
            }
            //設定照片解析度,注意要在攝像頭支援的範圍內選擇
            parameters.setPictureSize(list.get(size).width,list.get(size).height);
            //設定照相機引數
            mCamera.setParameters(parameters);
    
            //使用takePicture()方法完成拍照
            mCamera.autoFocus(new Camera.AutoFocusCallback() {
                //自動聚焦完成後拍照
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    if (success && camera != null){
                        mCamera.takePicture(new ShutterCallback(), null, new Camera.PictureCallback() {
                            //拍照回撥介面
                            @Override
                            public void onPictureTaken(byte[] data, Camera camera) {
                                savePhoto(data);
                                //停止預覽
                                mCamera.stopPreview();
                                //重啟預覽
                                mCamera.startPreview();
                            }
                        });
                    }
                }
            });
        }
    
       /* *//**
         * 快門回撥介面,如果不想拍照聲音,直接將new ShutterCallback()修改為null即可
         */
        private class ShutterCallback implements Camera.ShutterCallback {
            @Override
            public void onShutter() {
                MediaPlayer mPlayer = new MediaPlayer();
                mPlayer = MediaPlayer.create(getApplicationContext(), R.raw.shutter);
                try{
                    mPlayer.prepare();
                }catch (IllegalStateException e){
                    e.printStackTrace();
                }catch (IOException e){
                    e.printStackTrace();
                }
                mPlayer.start();
            }
        }
    

    這裡要注意下,有些手機呼叫onAutoFocus函式,會返回失敗,因為如果該手機無自動對焦,則無法執行對焦成功後的函式了。。。

  4. 當捕捉到資料後,便可以將這些資料儲存至設定的地方了:

        /**
         * 設定照片的路徑,具體路徑可自定義
         * @return
         */
        private String setPicSaveFile(){
            //建立儲存的路徑
            File storageDir = getOwnCacheDirectory(this,"MyCamera/photos");
            //返回自定義的路徑
            return storageDir.getPath();
        }
    
        private File getOwnCacheDirectory(Context context, String cacheDir) {
            File appCacheDir = null;
            //判斷SD卡正常掛載並且擁有根限的時候建立檔案
            if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) &&
                    hasExternalStoragePermission(context)){
                appCacheDir = new File(Environment.getExternalStorageDirectory(),cacheDir);
            }
            if (appCacheDir == null || !appCacheDir.exists() && !appCacheDir.mkdirs()){
                appCacheDir = context.getCacheDir();
            }
            return appCacheDir;
        }
    
        /**
         * 檢查是否有許可權
         * @param context
         * @return
         */
        private boolean hasExternalStoragePermission(Context context) {
            int permission = context.checkCallingOrSelfPermission("android.permission.WRITE_EXTERNAL_STORAGE");
            //PERMISSION_GRANTED=0
            return permission == 0;
        }
    

    由於呼叫了系統Camera和對SD卡讀寫,所以在AndroidManifest需要申請許可權:

    <!--攝像頭相關許可權-->
        <uses-permission android:name="android.permission.CAMERA" />
        <uses-feature android:name="android.hardware.camera" />
        <uses-feature android:name="android.hardware.camera.autofocus" />
        <!-- 在SDCard中建立與刪除檔案許可權 -->
        <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
        <!-- 往SDCard寫入資料許可權 -->
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    

     

 

後記:google在Android5.0後推出了Camera的升級版---Camera2:

按照Android的官方說明,camera 2支援以下5點新特性,有興趣的可以研究下:

(1)支援每秒30幀的全高清連拍。
(2)支援在每幀之間使用不同的設定。
(3)支援原生格式的影像輸出。
(4)支援零延遲快門和電影速拍。
(5)支援相機在其他方面的手動控制,比如設定噪音消除的級別。

最後也貼出本例子的原始碼吧,有興趣的可以到Github上下載本例子;如果支援本文,也可以直接在CSDN本站上下載github的連結地址:CSDN地址:https://download.csdn.net/download/toyauko/10636251

github傳送門:https://github.com/Liangzhuhua/MyCamera.git

相關文章