以前Camera2を使ってプレビューを表示するまでの最低限のコードを紹介しましたが、ここではそれに加えてキャプチャ(撮影)と写真の保存を行う方法を紹介しようと思います。

Camera2のコールバックの流れ

撮影に至るまでに大分苦労したので、Camera2のコールバックの流れを整理しておきます。下図の左はプレビューを表示するまでのコールバックの流れ、右はキャプチャをしたときのコールバックの流れになります。

シャッターボタンを用意して、ボタンが押されたら撮影した写真を保存する前提で書いています。ボタンのリスナからCameraCaptureSession.captureメソッドを呼び出して、キャプチャ(撮影)を実行します。

captureメソッドにはCameraCaptureSession.CaptureCallbackのインスタンスを指定します。キャプチャに成功するとこのコールバック関数が呼ばれるので、このコールバック関数内で撮影した画像を保存する処理を実装します。

 プレビューを表示する最低限のコード

Camera2を使ってプレビューを表示するまでの最低限のコードで紹介したコードに加えて以下のことを行います。

  • 対応しているファイル形式の確認
  • キャプチャ取得用のイメージリーダを作成してcreateCaptureRequestに渡すSurfaceのリストに追加する
  • シャッターボタンのリスナを追加
  • リスナからCameraSession.captureを実行する
  • CameraCaptureSession.CaptureCallback内で撮影画像を保存する

少し注意が必要なのがイメージリーダのインスタンスを作るのはキャプチャセッションを作る前でないといけないということです。そうでないと「java.lang.IllegalArgumentException: Bad argument passed to camera service」といったエラーが発生します。

以下がソースコードのイメージですが、プレビュー表示で紹介したコードに加えた部分だけを記載しています。

onCreateの実装イメージ。
対応ファイル形式の確認、キャプチャ用のイメージリーダの生成、シャッターボタンのリスナの登録をしています。

private CameraCaptureSession backCameraSession;
@Override
protected void onCreate(Bundle savedInstanceState) {
  ...
  // 対応しているファイル形式を確認
  StreamConfigurationMap configs = manager.getCameraCharacteristics(cameraId).get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
  int[] formats = configs.getOutputFormats();
  for (int i = 0; i < formats.length; i++) {
    if (formats[i] == ImageFormat.JPEG) {
      //JPEGに対応している場合
    }
  }
  ...
  // キャプチャ取得用のイメージリーダを作成
  ImageReader jpegImageReader = ImageReader.newInstance(640, 480, ImageFormat.JPEG, 1);
  ...
  // カメラ撮影ボタン押下時の処理
  ImageButton btn = (ImageButton) findViewById(R.id.cameraCapture);
  btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      // カメラセッションがあることを確認
      if (backCameraSession != null) {
        try {
          // 撮影リクエストの設定(ここは任意の設定)
          final CaptureRequest.Builder captureBuilder =
              backCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
          captureBuilder.addTarget(jpegImageReader.getSurface());
          captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
              CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);

          // 縦持の場合はオリエンテーションを設定
          captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, 90);

          // キャプチャ前にリピーティングのストップを行う
          backCameraSession.stopRepeating();
          // 撮影リクエスト 
          backCameraSession.capture(captureBuilder.build(), new CaptureCallback(), null);
        } catch (CameraAccessException e) {
          // 例外処理
        }
      }
    }
  });
}  

CameraDevice.StateCallbackの実装イメージ。
今までSurfaceViewだけをリストに加えていたのですが、先ほど生成したイメージリーダも登録するようにします。

private class OpenCameraCallback extends CameraDevice.StateCallback {
  @Override
  public void onOpened(CameraDevice cameraDevice) {
    ...
    // 写真保存用のイメージリーダをリストに追加
    surfaceList.add(jpegImageReader.getSurface());
    ...
  }
}

CameraCaptureSession.CaptureCallbackの実装イメージ。
イメージリーダからバイト配列を取得してファイルに保存します。

/**
* カメラ撮影時に呼ばれるコールバック関数
*/
private class CaptureCallback extends CameraCaptureSession.CaptureCallback {
  @Override
  public void onCaptureCompleted(CameraCaptureSession session,CaptureRequest request, TotalCaptureResult result) {
    // 画像バイナリの取得
    ByteBuffer buffer = jpegImageReader.acquireLatestImage().getPlanes()[0].getBuffer();
    byte[] bytes = new byte[buffer.capacity()];
    buffer.get(bytes);

    // 画像の書き込み
    OutputStream output = null;
    try {
      output = new FileOutputStream(new File("filename"));
      output.write(bytes);
    } catch (Exception e) {
      // 例外処理
    }

    // ImageReaderのクローズ
    jpegImageReader.close();
  }
}