Androidでカメラアプリを作成する(Android5.0以降に対応) (1)
Androidでカメラアプリを作成する(Android5.0以降に対応) (1)
主に使用するパッケージやクラス
android.hardware.camera2
Android5以上でカメラを使用するためのパッケージandroid.view.TextureView
カメラから取得した画像を連続描画するViewandroid.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