Android 開發 Camera1_拍照功能開發


前言    

  在開發Android應用的時候,如果需要調用攝像頭拍照或者錄像,除了通過Intent調用系統現有相機應用進行拍照錄像之外,還可以通過直接調用Camera硬件去去獲取攝像頭進行拍照錄像的操作。本篇博客將講解如何在Android應用中通過Camera拍照功能.

  錄像功能因為需要與MediaRecorder配使用,反而是更偏向操作MediaRecorder,所以我把錄像功能放到了音視頻開發篇幅里,請參考以下博客了解錄像功能開發.

  MediaRecorder視頻錄制入門:https://www.cnblogs.com/guanxinjing/p/10980906.html

  MediaRecorder與Camera1配合使用:https://www.cnblogs.com/guanxinjing/p/10986766.html

拍照開發

流程

  1. 獲取權限
  2. 初始化曲面視圖View(SurfaceView或者TextureView)(用於顯示相機預覽圖像)
  3. 初始化打開相機,選擇前后攝像頭
  4. 配置相機參數
  5. 拍照
  6. 處理照片返回數據,旋轉照片與壓縮照片

獲取權限

 <!-- 相機相關 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

拍照TextureView例子

其實目前最適合相機預覽圖像顯示使用的是TextureView,因為它是真正獨立刷新幀數的View,可以讓相機預覽減少卡頓問題

而使用TextureView需要開啟硬件加速功能.開啟硬件加速方法如下:

在AndroidManifest.xml 清單文件里,你需要實現相機功能的activity添加 android:hardwareAccelerated="true"

<activity android:name=".work.share.FaceCameraActivity"
            android:hardwareAccelerated="true"></activity>

以下是代碼部分:

 

public class FaceCameraActivity extends BaseActivity implements TextureView.SurfaceTextureListener,Camera.PictureCallback , View.OnClickListener{
    private static final float PICTURE_SIZE_PROPORTION = 1.1f;//目標分辨率尺寸
    private TextureView mTextureView;
    private Camera mCamera;
    private Button mBtnCamera;
    private boolean mMoveTakingPhotos = false; //用於防止連續點擊拍照多次引起報錯的問題


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initCamera();
        initCameraParameters();

    }

    @Override
    public int getLayout() {
        return R.layout.activity_face_camera;
    }

    @Override
    public void initView() {
        mTextureView = findViewById(R.id.texture_view);
        mBtnCamera = findViewById(R.id.btn_camera);
        mBtnCamera.setOnClickListener(this);
        mTextureView.setSurfaceTextureListener(this);//添加監聽,用於監聽TextureView的創建/變化/銷毀

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_camera:
                if(!mMoveTakingPhotos){
                    mMoveTakingPhotos = true;
                    mCamera.takePicture(null,null,this);//拍攝拍照 參數為快門圖片回調/原始圖片回調(未壓縮)/jpeg圖片回調
                }
                break;
            default:
                break;
        }

    }

    /**
     * 初始化打開相機
     */
    private void initCamera(){
        if (mCamera == null){
            //大多數情況下:0是后置攝像頭 1是前置攝像頭 ,這里demo就不弄這么復雜,在下面的會提供選擇前后攝像頭的方法
            mCamera = Camera.open(1);

        }

    }

    /**
     * 初始化相機參數
     */
    private void initCameraParameters(){
        Camera.Parameters parameters = mCamera.getParameters();
//        parameters.getPreviewSize();//當前預覽尺寸
//        parameters.getPictureSize();//當前分辨率尺寸
//        parameters.getJpegThumbnailSize(); //返回當前jpeg圖片中exif縮略圖的尺寸

//        parameters.getSupportedPreviewSizes();//預覽尺寸List
//        parameters.getSupportedPictureSizes();//分辨率尺寸List
//        parameters.getSupportedJpegThumbnailSizes();//返回當前jpeg圖片中exif縮略圖的尺寸List

        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
        Camera.Size previewSize = getpreviewSize(parameters, displayMetrics.heightPixels, displayMetrics.widthPixels);//獲取最接近屏幕分辨率的的預覽尺寸
        Camera.Size pictureSize = getPictureSize(parameters, PICTURE_SIZE_PROPORTION);//獲取對應比例的最大分辨率
        parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);//設置關閉閃光燈
        parameters.setFocusMode(Camera.Parameters.FLASH_MODE_AUTO); //對焦設置為自動
        parameters.setPictureFormat(PixelFormat.JPEG);//拍照格式
        parameters.setPreviewSize(previewSize.width, previewSize.height);//設置預覽尺寸
        parameters.setPictureSize(pictureSize.width, pictureSize.height);//分辨率尺寸
        parameters.set("orientation", "portrait");//相片方向
        parameters.set("rotation", 90); //相片鏡頭角度轉90度(默認攝像頭是橫拍)
        mCamera.setParameters(parameters);//添加參數
        mCamera.setDisplayOrientation(90);//設置顯示方向
    }

    /**
     * 計算獲得最接近比例的預覽尺寸
     * @param parameters
     * @param height
     * @param width
     * @return
     */
    private Camera.Size getpreviewSize(Camera.Parameters parameters, int height, int width){
        List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();
        Camera.Size selectPreviewSize = null; //緩存當前最准確比例的Size
        float currentDifference = 0; //緩存當前最小的差值
        /**
         * 下面這行代碼是求傳入高度和寬度的高寬比例,這里可以發現一個細節我下面的預覽尺寸求的的寬高比例.
         * 是的他們一個是高寬比一個是寬高比,說明為什么這樣,因為如果按照2個都是高寬比來獲得預覽尺寸你會發現,獲得的尺寸怎么都有可能會拉伸變形(除非狗屎運尺寸完美剛好)
         * 最好的辦法就是,不求最合適目標尺寸的長方形比例,而求一個最適合目標尺寸的正方形比例,這樣拉伸變形就不會出現了
         */
        float proportion = (float)height/(float)width;
        for (int i = 0; i < previewSizeList.size(); i++){
            Camera.Size size = previewSizeList.get(i);
            float previewSizeProportion = ((float)size.width)/((float)size.height); //計算當前預覽尺寸的寬高比例
            float tempDifference = Math.abs(previewSizeProportion - proportion); //相減求絕對值的差值
            if(i == 0){
                selectPreviewSize = size;
                currentDifference = tempDifference;
                continue;
            }
            if (tempDifference <= currentDifference){ //獲得最小差值
                if (tempDifference == currentDifference){ //如果差值一樣
                    if ((selectPreviewSize.width + selectPreviewSize.height) < (size.width+size.height)){ //判斷那個尺寸大保留那個
                        selectPreviewSize = size;
                        currentDifference = tempDifference;
                    }

                }else { //如果差值更小更准確
                    selectPreviewSize = size;
                    currentDifference = tempDifference;
                }
            }
            L.e("currentDifference="+currentDifference +"width="+selectPreviewSize.width+"height="+selectPreviewSize.height);

        }
        return selectPreviewSize;
    }

   /**
     * 計算獲得最接近比例的分辨率
     * @param parameters
     * @param targetProportion
     * @return
     */
    private Camera.Size getPictureSize(Camera.Parameters parameters, float targetProportion) {
        List<Camera.Size> pictureSizeList = parameters.getSupportedPictureSizes();
        Camera.Size selectPreviewSize = null;
        float currentDifference = 0;
        for (int i = 0; i < pictureSizeList.size(); i++) {
            Camera.Size size = pictureSizeList.get(i);
            L.e("分辨率列表_" + i + "_width=" + size.width + "height=" + size.height);
            float pictureSizeProportion = ((float) size.width) / ((float) size.height);
            L.e("分辨率列表_" + i + "_比例=" + pictureSizeProportion);
            float tempDifference = Math.abs(pictureSizeProportion - targetProportion);
            if (i == 0) {
                selectPreviewSize = size;
                currentDifference = tempDifference;
                continue;
            }
            if (tempDifference <= currentDifference) {
                if (tempDifference == currentDifference) {
                    if ((selectPreviewSize.width + selectPreviewSize.height) < (size.width + size.height)) { //判斷那個尺寸大保留那個
                        selectPreviewSize = size;
                        currentDifference = tempDifference;
                    }
                } else { //如果差值更小更准確
                    selectPreviewSize = size;
                    currentDifference = tempDifference;

                }
            }

        }
        L.e("當前選擇分辨率width=" + selectPreviewSize.width + "height=" + selectPreviewSize.height);
        return selectPreviewSize;
    }

     /**
     * 照片拍照完成后的回調方法
     * @param data
     * @param camera
     */
    @Override
    public void onPictureTaken(final byte[] data, Camera camera) {
        //注意!此處返回是主線程,而處理圖片是耗時操作需要放到子線程里處理
        handlerImageWaitDialog().show();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //這里有一個坑,如果你想要讀取照片的角度信息,那么就需要直接吧byte[] data的照片數據先保存成圖片文件在從文件讀成Bitmap
                    //因為如果你先處理壓縮圖片或者裁剪圖片,只要是Bitmap.createBitmap處理過就都有可能丟失這些照片信息到時候你怎么獲取角度都是0
                    FilePathSession.deleteFaceImageFile();
                    FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath());
                    fileOutputStream.write(data,0,data.length);
                    fileOutputStream.flush();
                    fileOutputStream.close();
                    int angle = readPictureDegree(FilePathSession.getFaceImagePath().toString()); //獲取角度
                    Bitmap bitmap = BitmapFactory.decodeFile(FilePathSession.getFaceImagePath().toString());//重新在文件里獲取圖片
                    Matrix matrix = new Matrix();//創建矩陣配置類,用與設置旋轉角度和旋轉位置
                    matrix.setRotate(angle, bitmap.getWidth(), bitmap.getHeight());//設置旋轉角度和旋轉位置
                    Bitmap handlerAngleBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
                    ImageHandle.bitmapImageConfig(handlerAngleBitmap)//這個是個人寫的壓縮圖片工具類
                            .setTargetKB(200)
                            .setSize(1080f,1920f)
                            .setHandleListener(new BitmapImageHandleListener() {
                                @Override
                                public boolean onReady(Bitmap inpBitmap) {
                                    return true;
                                }

                                @Override
                                public void onSuccess(Bitmap outBitmap) {
                                    try {
                                        FileOutputStream fileOutputStream = new FileOutputStream(FilePathSession.getFaceImagePath());
                                        outBitmap.compress(Bitmap.CompressFormat.JPEG,90,fileOutputStream);
                                        fileOutputStream.flush();
                                        fileOutputStream.close();
                                        outBitmap.recycle();
                                        runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            handlerImageWaitDialog().dismiss();
                                            Intent startFaceConfirm = new Intent(FaceCameraActivity.this, FaceConfirmActivity.class);
                                            startActivity(startFaceConfirm);
                                            FaceCameraActivity.this.finish();


                                        }
                                    });
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                        runOnUiThread(new Runnable() {
                                            @Override
                                            public void run() {
                                                mMoveTakingPhotos = false;
                                                handlerImageWaitDialog().dismiss();
                                                Toast.makeText(FaceCameraActivity.this, "圖像壓縮處理失敗", Toast.LENGTH_SHORT).show();
                                            }
                                        });
                                    }


                                }

                                @Override
                                public void onFailure(String text) {
                                    runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        mMoveTakingPhotos = false;
                                        handlerImageWaitDialog().dismiss();
                                        Toast.makeText(FaceCameraActivity.this, "圖像壓縮處理失敗", Toast.LENGTH_SHORT).show();
                                    }
                                });

                                }

                                @Override
                                public void onError(Exception e) {
                                    runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            mMoveTakingPhotos = false;
                                            handlerImageWaitDialog().dismiss();
                                            Toast.makeText(FaceCameraActivity.this, "圖像壓縮處理失敗", Toast.LENGTH_SHORT).show();
                                        }
                                    });

                                }
                            }).build();

                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mMoveTakingPhotos = false;
                            handlerImageWaitDialog().dismiss();
                            Toast.makeText(FaceCameraActivity.this, "圖像壓縮處理失敗", Toast.LENGTH_SHORT).show();
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mMoveTakingPhotos = false;
                            handlerImageWaitDialog().dismiss();
                            Toast.makeText(FaceCameraActivity.this, "圖像壓縮處理失敗", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            }
        }).start();

    }

    /**
     * TextureView的創建完成后的可用狀態回調
     */
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        //可用
        try {
            mCamera.setPreviewTexture(surface);//給相機添加預覽圖像的曲面View
            mCamera.startPreview();//啟動圖像預覽
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        //尺寸變化

    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        //銷毀
        return false;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        //更新

    }
    
    /**
     * 讀取照片旋轉角度
     *
     * @param path 照片路徑
     * @return 角度
     */
    public int readPictureDegree(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    L.e("觸發 90度");
                    degree = 90;
                    degree = degree + 90;//這里我是直接處理要增加或者減少的角度,讓圖片豎起來
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    L.e("觸發 180度");
                    degree = 180;
                    degree = degree + 0;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    L.e("觸發 270度");
                    degree = 270;
                    degree = degree - 90;
                    break;
                default:
                    L.e("觸發 0度");
                    degree = 0;
                    degree = degree + 180;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

    @Override
    protected void onResume() {
        super.onResume();
//        try {
//            mCamera.reconnect(); //相機重連接
//            mCamera.startPreview();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
    }

    @Override
    protected void onStop() {
        super.onStop();
//        mCamera.stopPreview();暫停預覽
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mCamera != null){
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }
}

  選擇前后攝像頭的代碼

    /**
     * 選擇攝像頭
     * @param isFacing true=前攝像頭 false=后攝像頭
     * @return 攝像id
     */
    private Integer selectCamera(boolean isFacing){
        int cameraCount = Camera.getNumberOfCameras();
//        CameraInfo.CAMERA_FACING_BACK 后攝像頭
//        CameraInfo.CAMERA_FACING_FRONT  前攝像頭
        int facing = isFacing ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
        Log.e(TAG, "selectCamera: cameraCount="+cameraCount);
        if (cameraCount == 0){
            Log.e(TAG, "selectCamera: The device does not have a camera ");
            return null;
        }
        Camera.CameraInfo info = new Camera.CameraInfo();
        for (int i=0; i < cameraCount; i++){
            Camera.getCameraInfo(i,info);
            if (info.facing == facing){
                return i;
            }

        }
        return null;


    }

 如果你需要切換攝像頭

mCamera.stopPreview();//暫停預覽
mCamera.release();//釋放攝像頭 這個是關鍵
openCamera(selectCamera(mCurrentCameraFacing));//重新選擇攝像頭並且打開
configCameraParameters();//重新配置攝像頭參數
startPreview(mSurfaceTexture);//開啟預覽

拍照SurfaceView例子

package com.demo;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

import com.example.user.demo.R;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class TakePictureActivity extends AppCompatActivity implements SurfaceHolder.Callback,Camera.PictureCallback {
    private static final String TAG = "TakePictureActivity";
    private Button mBtnTake;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Camera mCamera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_take_picture);
        mBtnTake = (Button)findViewById(R.id.take);
        mSurfaceView = (SurfaceView)findViewById(R.id.surfaceView);
        init();
        mBtnTake.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mCamera != null){
                    mCamera.takePicture(null,null,TakePictureActivity.this);
                }
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSurfaceHolder.removeCallback(this);
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();
        mCamera.release();

    }

    private void init(){
        mSurfaceHolder = mSurfaceView.getHolder();
        mCamera = Camera.open();
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceHolder.addCallback(this);
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setFlashMode("off");
        parameters.setPictureFormat(PixelFormat.JPEG); //設定相片格式為JPEG,默認為NV21
        parameters.setPreviewSize(640, 480);
        parameters.set("orientation", "portrait");//相片方向
        parameters.set("rotation", 90); //相片鏡頭角度轉90度(默認攝像頭是橫拍)
        mCamera.setParameters(parameters);
        mCamera.setDisplayOrientation(90);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.e(TAG,"懸浮窗口生成");
        try {
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mCamera.startPreview();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.e(TAG,"懸浮窗口變化");

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.e(TAG,"懸浮窗口銷毀");

    }

    @Override
    public void onPictureTaken(final byte[] data, Camera camera) {
        Log.e(TAG,"拍照結果處理");
        File file = getExternalFilesDir("takePiceture");
        if (!file.exists()){
            file.mkdirs();
        }
        final File fileName = new File(file,System.currentTimeMillis()+".jpg");
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    FileOutputStream fileOutputStream = new FileOutputStream(fileName);
                    fileOutputStream.write(data);
                    fileOutputStream.close();
                    mCamera.startPreview();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

}

Camera api 說明

  Camera是Android攝像頭硬件的相機類,位於硬件包"android.hardware.Camera"下。它主要用於攝像頭捕獲圖片、啟動/停止預覽圖片、拍照、獲取視頻幀等,它是設備本地的服務,負責管理設備上的攝像頭硬件。

  Camera既然用於管理設備上的攝像頭硬件,那么它也為開發人員提供了相應的方法,並且這些方法大部分都是native的,用C++在底層實現,下面簡單介紹一下Camera的一些方法:

api 說明
open() 打開Camera,返回一個Camera實例。
open(int cameraId) 根據cameraId打開一個Camera,返回一個Camera實例。
release() 釋放掉Camera的資源。
getNumberOfCameras() 獲取當前設備支持的Camera硬件個數。
getParameters() 獲取Camera的各項參數設置類。
setParameters(Camera.Parameters params) 通過params把Camera的各項參數寫入到Camera中。
setDisplayOrientation(int degrees) 攝像預覽的旋轉度。
setPreviewDisplay(SurfaceHolder holder) 設置Camera預覽的SurfaceHolder。
starPreview() 開始Camera的預覽。
stopPreview() 停止Camera的預覽
setPreviewCallback() 設置預覽回調
reconnect() 重新連接
autoFocus(Camera.AutoFocusCallback cb) 自動對焦
cancelAutoFocus() 取消啟動對焦
setAutoFocusMoveCallback() 自動對焦移動回調
takePicture(Camera.ShutterCallback shutter,Camera.PictureCallback raw,Camera.PictureCallback jpeg) 拍照。
enableShutterSound() 啟用快門聲音
lock() 鎖定Camera硬件,使其他應用無法訪問。
unlock() 解鎖Camera硬件,使其他應用可以訪問。
startFaceDetection() 啟動人臉識別
stopFaceDetection() 停止人臉識別
setFaceDetectionListener() 人臉識別監聽回調
setPreviewCallback() 設置預覽回調
setPreviewCallbackWithBuffer() 設置預覽緩沖回調
setOneShotPreviewCallback() 設置一個鏡頭預覽回調
setErrorCallback() 設置異常回調
startSmoothZoom() 啟動平滑縮放
stopSmoothZoom() 停止平滑縮放
setZoomChangeListener() 縮放監聽
setPreviewTexture() 設置預覽紋理

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 

 

 

 

 

 

 

上面已經介紹了Camera的常用方法,下面根據這些方法詳細講解Android下使用Camera開發拍照應用最基本的過程:

  1. 使用open()方法獲取一個Camera對象,鑒於Android設備可能配置了多個攝像頭,open()方法可以通過攝像頭Id開啟指定的攝像頭。
  2. 為Camera對象設置預覽類,它是一個SurfaceHolder對象,通過setPreviewDisplay(SurfaceHolder)方法設置。
  3. 調用startPreview()方法開始Camera對象的預覽。
  4. 調用takePicture()方法進行拍照,其中可以通過Camera.PictureCallback()回調獲得拍攝的Image數據。
  5. 當拍攝完成后,需要調用stopPreview()方法停止預覽,並使用release()釋放Camera占用的資源。   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM