カメラを使ってAndroidアプリを作ろうと思い、なんとかプレビュー表示までできるようになったので最低限のコードと補足を記載します。

Camera2と参考情報について

Android5.0(API level 21)以降はCamera2というAPIを利用してアプリを開発することができるようです。カメラAPIについてはここにガイドラインがありますが、Camera2についてはリファレンスやgoogleのブログ公式サンプルなどを参考に開発していく必要があります。

とりあえず今後はCamera2になっていくだろうということで、Camera2で開発をしてみました。

SurfaceViewでもCamera2でプレビューを表示できる

Camera2についていくつかサイトを調査していたのですが、Camera2ではSurfaceViewにプレビューを表示できないという記事がいくつかありました。が、そんなことはなくSurfaceViewにプレビュー表示できており、本記事のサンプルコードはSurfaceViewにプレビュー表示する内容になっています。

性能的にSurfaceViewはよろしくないという話もあったりしますが・・・

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

Camera2はコールバックで処理が進んでいくような仕様になっています。以下のコードはアクティビティ起動時にカメラを起動してプレビューを表示する内容で、onCreate内のコードになっています。カメラの起動に成功または失敗したらOpenCameraCallbackと定義したコールバック関数が呼ばれるようになっています。

private CameraDevice backCameraDevice;
private CameraCaptureSession backCameraSession;

@Override
protected void onCreate(Bundle savedInstanceState) {
  CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
  
  try {
    //カメラIDを取得(背面カメラを選択)
    String backCameraId;
    for (String cameraId : manager.getCameraIdList()) {
      CameraCharacteristics chars
              = manager.getCameraCharacteristics(cameraId);
      Integer facing = chars.get(CameraCharacteristics.LENS_FACING);

      if (facing != null && facing ==
              CameraCharacteristics.LENS_FACING_BACK) {
          backCameraId = cameraId;
      }
    }
    
    // SurfaceViewにプレビューサイズを設定する(サンプルなので適当な値です)
    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
    surfaceView.getHolder().setFixedSize(640, 320);
    
    // カメラオープン(オープンに成功したときに第2引数のコールバッククラスが呼ばれる)
    manager.openCamera(backCameraId, new OpenCameraCallback(), null);
  } catch (CameraAccessException e) {
    //例外処理を記述
  }
}

//ホームボタンを押した際などにカメラを開放する(開放しないと裏で動き続ける・・・)
@Override
protected void onPause() {
  super.onPause();
  // カメラセッションの終了
  if (backCameraSession != null) {
    try {
      backCameraSession.stopRepeating();
    } catch (CameraAccessException e) {
      Log.e(TAG, e.toString());
    }
    backCameraSession.close();
  }

  // カメラデバイスとの切断
  if (backCameraDevice != null) {
    backCameraDevice.close();
  }
}

次にOpenCameraCallbackの定義になります。この中ではカメラのキャプチャを流すSurfaceリストを作り、そのリストを対象にキャプチャセッションを開始します。今回はプレビュー用のSurfaceViewだけをリストに登録していますが、実際にJPEG画像などに保存する際はリストにImageReaderも登録する必要があります。(今後の記事で記載予定)

キャプチャセッション開始時にコールバッククラスを引数に指定しますが、セッション開始後にこのコールバッククラスが呼ばれます。

/**
 * カメラデバイス接続完了後のコールバッククラス
 */
private class OpenCameraCallback extends CameraDevice.StateCallback {
  @Override
  public void onOpened(CameraDevice cameraDevice) {
    backCameraDevice = cameraDevice;

    // プレビュー用のSurfaceViewをリストに登録
    SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
    ArrayList<Surface> surfaceList = new ArrayList();
    surfaceList.add(surfaceView.getHolder().getSurface());

    try {
      // プレビューリクエストの設定(SurfaceViewをターゲットに)
      mPreviewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
      mPreviewRequestBuilder.addTarget(surfaceView.getHolder().getSurface());

      // キャプチャーセッションの開始(セッション開始後に第2引数のコールバッククラスが呼ばれる)
      cameraDevice.createCaptureSession(surfaceList, new CameraCaptureSessionCallback(), null);
      
    } catch (CameraAccessException e) {
        // エラー時の処理を記載
    }
  }

  @Override
  public void onDisconnected(CameraDevice cameraDevice) {
    // 切断時の処理を記載
  }

  @Override
  public void onError(CameraDevice cameraDevice, int error) {
    // エラー時の処理を記載
  }
}

次にCameraCaptureSessionCallbackのコールバッククラスの定義になります。ここでは、プレビュー時のAF設定等を任意で行い最後にsetRepeatingRequestを呼び出してプレビューを開始します。この関数の第2引数に撮影時のコールバッククラスを指定しますが、とりあえずプレビューを出すまでですのでコールバッククラスの中身は空にしています。

/**
 * カメラが起動し使える状態になったら呼ばれるコールバック
 */
private class CameraCaptureSessionCallback extends CameraCaptureSession.StateCallback {
  @Override
  public void onConfigured(CameraCaptureSession session) {
    backCameraSession = session;

    try {
      // オートフォーカスの設定
      mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
              CameraMetadata.CONTROL_AF_TRIGGER_START);

      // プレビューの開始(撮影時に第2引数のコールバッククラスが呼ばれる)
      session.setRepeatingRequest(mPreviewRequestBuilder.build(), new CaptureCallback(), null);

    } catch (CameraAccessException e) {
        Log.e(TAG, e.toString());
    }
  }

  @Override
  public void onConfigureFailed(CameraCaptureSession session) {
    //失敗時の処理を記載
  }
}

/**
 * カメラ撮影時に呼ばれるコールバック関数
 */
private class CaptureCallback extends CameraCaptureSession.CaptureCallback {
}

長くなりましたが、これでSurfaceViewにカメラプレビューが表示できました。次は撮影まで実装できたら続きを書こうと思います。