ケルベロスさんのプログラミング / けるぷろ

プログラマのけるさんことケルベロスです

Androidでカメラアプリを作成する(Android5.0以降に対応) (1)

Androidでカメラアプリを作成する(Android5.0以降に対応) (1)

主に使用するパッケージやクラス

  • android.hardware.camera2
    Android5以上でカメラを使用するためのパッケージ

  • android.view.TextureView
    カメラから取得した画像を連続描画するView

  • android.hardware.camera2.CameraManager
    カメラデバイス全体を管理するクラス

  • android.hardware.camera2.CameraDevice
    端末毎のカメラオデバイス(前面カメラや背面カメラそれぞれ)

  • android.hardware.camera2.CameraCaptureSession
    カメラデバイスの画像を取得するセッション

開発環境

Android 5.0 ~
Android Studio 2.3.3

カメラを使用する流れ

  • TextureViewをアクティブにする
  • CameraManagerをContextから取得してCameraDeviceを起動する
  • CameraDeviceからCameraCaptureSessionを作成する
  • CameraCaptureSessionから取得した画像をTextureViewに描画していく

準備

Manifestに追加する

<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" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera2.full" />

CameraFragmentの実装

Viewの準備

.. 省略

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_camera, container, false);
    mTextureView = (TextureView) view.findViewById(R.id.texture_view);
    mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);      // ->(1)

    Button button = (Button) view.findViewById(R.id.button_take_picture);
    button.setOnClickListener(mButtonOnCickListener);                     // ->(2)

    return view;
}

.. 省略

(1) TextureViewのステータス変更のコールバックを登録する(後述)
(2) 撮影ボタンのリスナ登録 (省略)

TextureViewのステータス変更のコールバック

.. 省略

private TextureView.SurfaceTextureListener mSurfaceTextureListener 
    = new TextureView.SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i2) {     // ->(3)
            prepareCameraView();                                                                  // ->(4)
        }

        @Override
        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, int i, int i2) { } // ->(3)
        @Override
        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { return false; } // ->(6)
        @Override
        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { }                    // ->(7)
    };

.. 省略

TextureView.SurfaceTextureListenerを使用する
(3) TexutureViewの準備が完了
(4) CameraDeviceの準備を行う
(5) サイズ変更通知 今回は無視
(6) 破棄 今回は無視
(7) 更新 今回は無視

CameraDeviceの準備

.. 省略

private CameraDevice mCameraDevice; // ->(8)
private Size mPreviewSize;

.. 省略

private void prepareCameraView() {
    CameraManager cameraManager 
        = (CameraManager) mParentActivity.getSystemService(Context.CAMERA_SERVICE); // ->(9)
    try {
        String backCameraId = null;
        for (String cameraId : cameraManager.getCameraIdList()) {                   // ->(10)
            CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
            if (characteristics.get(CameraCharacteristics.LENS_FACING)
                    == CameraCharacteristics.LENS_FACING_BACK) {                    // ->(11)
                backCameraId = cameraId;

                StreamConfigurationMap map 
                    = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); // ->(12)
                mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0];
            }
        }

        if (backCameraId == null) {
            return;
        }

        if (ActivityCompat.checkSelfPermission(mParentActivity, Manifest.permission.CAMERA) // ->(13)
                != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        cameraManager.openCamera(backCameraId, new CameraDevice.StateCallback() { // ->(14)
                    @Override
                    public void onOpened(@NonNull CameraDevice cameraDevice) {    // ->(15)
                        mCameraDevice = cameraDevice;
                        createCameraCaptureSession();
                    }

                    @Override
                    public void onDisconnected(@NonNull CameraDevice cameraDevice) { // ->(16)

                    }

                    @Override
                    public void onError(@NonNull CameraDevice cameraDevice, int i) { // ->(17)

                    }
        }, null);

    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

.. 省略

CameraManagerからカメラ情報を取得してカメラの準備をする
(8) メンバ変数にCameraDeviceを定義
(9) システムサービスのCameraManagerを取得する
(10) getCameraIdListで配列で端末の使用するカメラのIDを取得できる
(11) 背面カメラのIDを取得
(12) 背面カメラのプレビュー画面サイズを取得
(13) パーミッションの確認
(14) 背面カメラのIDを使用してカメラをアクティブにする
(15) 成功のコールバック CameraCaptureSessionを有効化する
(16) 接続終了
(17) 失敗

CameraCaptureSessionの有効化

.. 省略

private void createCameraCaptureSession() {
    if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) {
        return;
    }

    SurfaceTexture texture =  mTextureView.getSurfaceTexture();                     // ->(18)
    if (null == texture) {
        return;
    }
    texture.setDefaultBufferSize(mPreviewSize.getWidth(),mPreviewSize.getHeight());
    Surface surface = new Surface(texture);

    try {
        mCaptureRequestBuilder 
            = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);  // ->(19)
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
    mCaptureRequestBuilder.addTarget(surface);

    try {
        mCameraDevice.createCaptureSession(Arrays.asList(surface), 
        new CameraCaptureSession.StateCallback() { // ->(20)
            @Override
            public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // ->(21)
                mCaptureSession = cameraCaptureSession;
                updatePreview();
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { // ->(22)
                Toast.makeText(mParentActivity, "onConfigureFailed", Toast.LENGTH_LONG).show();
            }
        }, null);
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

.. 省略

CameraDeviceからキャプチャを取得するためのセッションを確立する
(18) サイズを設定してTextureViewからSurfaceViewを初期化する
(19) CameraDeviceからCaptureRequestを生成してプレビュー画面をターゲットに設定する
(20) SurfaceViewを使用してCameraDeviceとのセッションを確立する
(21) 確立成功 画面の更新を開始する
(22) 失敗

CaptureSessionから画像を繰り返し取得してTextureViewに表示する

.. 省略

private void updatePreview() {
    if (null == mCameraDevice) {
        return;
    }
    mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);                 // ->(23)

    HandlerThread thread = new HandlerThread("CameraCapture");              // ->(24)
    thread.start();
    Handler backgroundHandler = new Handler(thread.getLooper()); 

    try {
        mCaptureSession.setRepeatingRequest(mCaptureRequestBuilder.build(),null,backgroundHandler); // ->(25)
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
}

.. 省略

CaptureSessionから画像を繰り返し取得してTextureViewに表示する
(23)オートフォーカスを有効にするための設定
(24)バックグラウンドで実行するためのスレッドを作成
(25)リクエストを開始する

以上がカメラを起動するためのコードです。
このフラグメントを表示する前にカメラのパーミッションをユーザに要求する必要があります。

カメラのパーミッションをリクエストする

今回は呼び出し元のMainActivityに実装します

.. 省略

private final int MY_CAMERA_REQUEST_CODE = 1001;

.. 省略

private void showCameraFragment() {
    if (checkSelfPermission(Manifest.permission.CAMERA) 
        != PackageManager.PERMISSION_GRANTED) {                                            // ->(26)
        requestPermissions(new String[]{Manifest.permission.CAMERA},MY_CAMERA_REQUEST_CODE);     // ->(27)
        return;
    }

    CameraFragment f = new CameraFragment();                                                   // ->(28)
    this.setFragment(f);
}

@Override
public void onRequestPermissionsResult(int requestCode, 
                                        @NonNull String[] permissions,
                                        @NonNull int[] grantResults) {                     // ->(29)
    if (requestCode != MY_CAMERA_REQUEST_CODE) {
        return;
    }
    if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {                             // ->(30)

        CameraFragment f = new CameraFragment();
        this.setFragment(f);

    } else {                                                                                 // ->(31)

    }
}

.. 省略

(26) すでに許可済みか判定する
(27) リクエスト開始
(28) 許可済みであればそのまま起動 (this.setFragmentの内容は省略)
(29) パーミッションのリクエスト結果のコールバックをOverrideしておく
(30) 成功 CameraFragmentを起動する
(31) 失敗

まとめ

以上でカメラを起動して画像を画面に写すという処理が完了しました。
実際に撮影して画像を保存する処理は後ほど実装していきます。
あと、画面の回転に対応していないのでそちらも後ほど実装していきます。

サンプルコード

githubにありますので参考にしていただければと思います
https://github.com/rdme/PictureSample