Android Camera2 拍照入門學習

學習資料:


2017 月 12 月 16 號
補充一個學習資料


20170513 16:25

注意權限,注意權限,注意權限

5.0在配置文件申明一下就好,6.0以上的系統需要考慮動態權限問題

評論里2個同學說因為權限問導致了黑屏,排查代碼卻沒有發現問題,對剛剛接觸安卓不久的同學造成困擾,還耽擱了不少時間,抱歉了

具體的相機設置、預覽控件大小以及樣張的參數之類的,需要根據自己的手機、系統做些相應調整。我測試手機是5.1系統,使用博客中的代碼只是簡單地拍了個照片測試了下,除了照片樣張質量一般外,暫時沒有發現其他問題。我并沒有在項目中實際開發過拍照相關的功能,這里我也是簡單地學習下。若哪位同學發現代碼里的問題,請留言指出,免得造成誤導浪費時間


Android 5.0(21)之后,android.hardware.Camera被廢棄(下面稱為Camera1),還有一個android.graphics.Camera,這個android.graphics.Camera不是用來照相的,是用來處理圖像的,可以做出3D的圖像效果之類的,之前的Camera1則由android.hardware.Camera2來代替

Camera2支持RAW輸出,可以調節曝光,對焦模式,快門等,功能比原先Camera強大


1. Camera1使用

使用步驟:

  1. 調用Camera.open(),打開相機,默認為后置,可以根據攝像頭ID來指定打開前置還是后置
  2. 調用Camera.getParameters()得到一個Camera.Parameters對象
  3. 使用步驟2得到的Camera.Parameters對象,對拍照參數進行設置
  4. 調用Camera.setPreviewDispaly(SurfaceHolder holder),指定使用哪個SurfaceView來顯示預覽圖片
  5. 調用Camera.startPreview()方法開始預覽取景
  6. 調用Camera.takePicture()方法進行拍照
  7. 拍照結束后,調用Camera.stopPreview()結束取景預覽,之后再replease()方法釋放資源

這幾個步驟從瘋狂Android講義中學到


1.1 簡單使用

使用SurfaceView進行取景的預覽,點擊屏幕進行拍照,用ImageView來展示拍的照片

取景

拍照預覽

想買關于操作系統和C的書看,知乎很多人推薦這兩本,就買了。然而,深入理解操作系統買早了,啃不動,看不懂

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/surface_view_camera2_activity"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <ImageView
        android:id="@+id/iv_show_camera2_activity"
        android:layout_width="180dp"
        android:layout_height="320dp"
        android:visibility="gone"
        android:layout_centerInParent="true"
        android:scaleType="centerCrop" />
</RelativeLayout>

Activity代碼:

public class CameraActivity extends AppCompatActivity implements View.OnClickListener {

    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private Camera mCamera;
    private ImageView iv_show;
    private int viewWidth, viewHeight;//mSurfaceView的寬和高

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera2);
        initView();
    }

    /**
     * 初始化控件
     */
    private void initView() {
        iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
        //mSurfaceView
        mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
        mSurfaceHolder = mSurfaceView.getHolder();
        // mSurfaceView 不需要自己的緩沖區
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        // mSurfaceView添加回調
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) { //SurfaceView創建
                // 初始化Camera
                initCamera();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView銷毀
                // 釋放Camera資源
                if (mCamera != null) {
                    mCamera.stopPreview();
                    mCamera.release();
                }
            }
        });
        //設置點擊監聽
        mSurfaceView.setOnClickListener(this);
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (mSurfaceView != null) {
            viewWidth = mSurfaceView.getWidth();
            viewHeight = mSurfaceView.getHeight();
        }
    }

    /**
     * SurfaceHolder 回調接口方法
     */
    private void initCamera() {
        mCamera = Camera.open();//默認開啟后置
        mCamera.setDisplayOrientation(90);//攝像頭進行旋轉90°
        if (mCamera != null) {
            try {
                Camera.Parameters parameters = mCamera.getParameters();
                //設置預覽照片的大小
                parameters.setPreviewFpsRange(viewWidth, viewHeight);
                //設置相機預覽照片幀數
                parameters.setPreviewFpsRange(4, 10);
                //設置圖片格式
                parameters.setPictureFormat(ImageFormat.JPEG);
                //設置圖片的質量
                parameters.set("jpeg-quality", 90);
                //設置照片的大小
                parameters.setPictureSize(viewWidth, viewHeight);
                //通過SurfaceView顯示預覽
                mCamera.setPreviewDisplay(mSurfaceHolder);
                //開始預覽
                mCamera.startPreview();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 點擊回調方法
     */
    @Override
    public void onClick(View v) {
        if (mCamera == null) return;
        //自動對焦后拍照
        mCamera.autoFocus(autoFocusCallback);
    }


    /**
     * 自動對焦 對焦成功后 就進行拍照
     */
    Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            if (success) {//對焦成功
              
                camera.takePicture(new Camera.ShutterCallback() {//按下快門
                    @Override
                    public void onShutter() {
                      //按下快門瞬間的操作
                    }
                }, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {//是否保存原始圖片的信息

                    }
                }, pictureCallback);
            }
        }
    };
    /**
     * 獲取圖片
     */
    Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            final Bitmap resource = BitmapFactory.decodeByteArray(data, 0, data.length);
            if (resource == null) {
                Toast.makeText(CameraActivity.this, "拍照失敗", Toast.LENGTH_SHORT).show();
            }
            final Matrix matrix = new Matrix();
            matrix.setRotate(90);
            final Bitmap bitmap = Bitmap.createBitmap(resource, 0, 0, resource.getWidth(), resource.getHeight(), matrix, true);
            if (bitmap != null && iv_show != null && iv_show.getVisibility() == View.GONE) {
                mCamera.stopPreview();
                iv_show.setVisibility(View.VISIBLE);
                mSurfaceView.setVisibility(View.GONE);
                Toast.makeText(CameraActivity.this, "拍照", Toast.LENGTH_SHORT).show();
                iv_show.setImageBitmap(bitmap);
            }
        }
    };
}

權限:

<uses-permission android:name="android.permission.CAMERA" />

在獲得圖片后,想要顯示的效果是照片是豎直顯示,resource顯示的卻是逆時針旋轉了90°,照片是橫著的,就使用matrix.setRotate(90)進行旋轉


2. Camera2

Camera2拍照示意圖

這里引用了管道的概念將安卓設備和攝像頭之間聯通起來,系統向攝像頭發送 Capture 請求,而攝像頭會返回 CameraMetadata。這一切建立在一個叫作 CameraCaptureSession 的會話中。

camera2中主要的類

以上從極客學院android.hardware.camera2 使用指南摘抄

  • CameraManaer 攝像頭管理器,用于檢測攝像頭,打開系統攝像頭,調用CameraManager.getCameraCharacteristics(String)可以獲取指定攝像頭的相關特性
  • CameraCharacteristics 攝像頭的特性
  • CameraDevice 攝像頭,類似android.hardware.Camera也就是Camera1Camera
  • CameraCaptureSession 這個對象控制攝像頭的預覽或者拍照,setRepeatingRequest()開啟預覽,capture()拍照,CameraCaptureSession提供了StateCallback、CaptureCallback兩個接口來監聽CameraCaptureSession的創建和拍照過程。
  • CameraRequest和CameraRequest.Builder,預覽或者拍照時,都需要一個CameraRequest對象。CameraRequest表示一次捕獲請求,用來對z照片的各種參數設置,比如對焦模式、曝光模式等。CameraRequest.Builder用來生成CameraRequest對象。

以上從腎虛將軍的android camera2 詳解說明摘抄


2.1 簡單使用

使用的依然是SurfaceView來進行展示預覽

主要思路:

  1. 獲得攝像頭管理器CameraManager mCameraManagermCameraManager.openCamera()來打開攝像頭
  2. 指定要打開的攝像頭,并創建openCamera()所需要的CameraDevice.StateCallback stateCallback
  3. CameraDevice.StateCallback stateCallback中調用takePreview(),這個方法中,使用CaptureRequest.Builder創建預覽需要的CameraRequest,并初始化了CameraCaptureSession,最后調用了setRepeatingRequest(previewRequest, null, childHandler)進行了預覽
  4. 點擊屏幕,調用takePicture(),這個方法內,最終調用了capture(mCaptureRequest, null, childHandler)
  5. new ImageReader.OnImageAvailableListener(){}回調方法中,將拍照拿到的圖片進行展示

代碼:

public class Camera2Activity extends AppCompatActivity implements View.OnClickListener {
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    ///為了使照片豎直顯示
    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;
    private ImageView iv_show;
    private CameraManager mCameraManager;//攝像頭管理器
    private Handler childHandler, mainHandler;
    private String mCameraID;//攝像頭Id 0 為后  1 為前
    private ImageReader mImageReader;
    private CameraCaptureSession mCameraCaptureSession;
    private CameraDevice mCameraDevice;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera2);
        initVIew();
    }

    /**
     * 初始化
     */
    private void initVIew() {
        iv_show = (ImageView) findViewById(R.id.iv_show_camera2_activity);
        //mSurfaceView
        mSurfaceView = (SurfaceView) findViewById(R.id.surface_view_camera2_activity);
        mSurfaceView.setOnClickListener(this);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.setKeepScreenOn(true);
        // mSurfaceView添加回調
        mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) { //SurfaceView創建
                // 初始化Camera
                initCamera2();
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) { //SurfaceView銷毀
                // 釋放Camera資源  
                if (null != mCameraDevice) {    
                    mCameraDevice.close(); 
                    Camera2Activity.this.mCameraDevice = null;
                }
            }
        });
    }

    /**
     * 初始化Camera2
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void initCamera2() {
        HandlerThread handlerThread = new HandlerThread("Camera2");
        handlerThread.start();
        childHandler = new Handler(handlerThread.getLooper());
        mainHandler = new Handler(getMainLooper());
        mCameraID = "" + CameraCharacteristics.LENS_FACING_FRONT;//后攝像頭
        mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.JPEG,1);
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //可以在這里處理拍照得到的臨時照片 例如,寫入本地
            @Override
            public void onImageAvailable(ImageReader reader) {
                mCameraDevice.close();
                mSurfaceView.setVisibility(View.GONE);
                iv_show.setVisibility(View.VISIBLE);
                // 拿到拍照照片數據
                Image image = reader.acquireNextImage();
                ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);//由緩沖區存入字節數組
                final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
                if (bitmap != null) {
                    iv_show.setImageBitmap(bitmap);
                }
            }
        }, mainHandler);
        //獲取攝像頭管理
        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            //打開攝像頭
            mCameraManager.openCamera(mCameraID, stateCallback, mainHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 攝像頭創建監聽
     */
    private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {//打開攝像頭
            mCameraDevice = camera;
            //開啟預覽
            takePreview();
        }

        @Override
        public void onDisconnected(CameraDevice camera) {//關閉攝像頭
            if (null != mCameraDevice) {
                mCameraDevice.close();
                Camera2Activity.this.mCameraDevice = null;
            }
        }

        @Override
        public void onError(CameraDevice camera, int error) {//發生錯誤
            Toast.makeText(Camera2Activity.this, "攝像頭開啟失敗", Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * 開始預覽
     */
    private void takePreview() {
        try {
            // 創建預覽需要的CaptureRequest.Builder
            final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            // 將SurfaceView的surface作為CaptureRequest.Builder的目標
            previewRequestBuilder.addTarget(mSurfaceHolder.getSurface());
            // 創建CameraCaptureSession,該對象負責管理處理預覽請求和拍照請求
            mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
            {
                @Override
                public void onConfigured(CameraCaptureSession cameraCaptureSession) {
                    if (null == mCameraDevice) return;
                    // 當攝像頭已經準備好時,開始顯示預覽
                    mCameraCaptureSession = cameraCaptureSession;
                    try {
                        // 自動對焦
                        previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                        // 打開閃光燈
                        previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                        // 顯示預覽
                        CaptureRequest previewRequest = previewRequestBuilder.build();
                        mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
                    Toast.makeText(Camera2Activity.this, "配置失敗", Toast.LENGTH_SHORT).show();
                }
            }, childHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 點擊事件
     */
    @Override
    public void onClick(View v) {
        takePicture();
    }

    /**
     * 拍照
     */
    private void takePicture() {
        if (mCameraDevice == null) return;
       // 創建拍照需要的CaptureRequest.Builder
        final CaptureRequest.Builder captureRequestBuilder;
        try {
            captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            // 將imageReader的surface作為CaptureRequest.Builder的目標
            captureRequestBuilder.addTarget(mImageReader.getSurface());
            // 自動對焦
            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            // 自動曝光
            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
            // 獲取手機方向
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            // 根據設備方向計算設置照片的方向
            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
            //拍照
            CaptureRequest mCaptureRequest = captureRequestBuilder.build();
            mCameraCaptureSession.capture(mCaptureRequest, null, childHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}

布局代碼以及權限與Camera1中一樣,效果一樣

預覽時,是將mSurfaceHolder.getSurface()作為目標
顯示拍照結果時,是將mImageReader.getSurface()作為目標


3. 最后

Camera2的功能很強大,暫時也只是學習了最基本的思路

住的地方,沒有桌子,于是坐地上,趴在床上敲代碼,腰疼。逛淘寶買桌子去

感謝極客學院和腎虛將軍的學習資料

共勉 : )

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容