【今更だけどandroid.hardware.Cameraを使う】カメラプレビューを表示する

このエントリーを Google ブックマーク に追加
Pocket
[`yahoo` not found]

android.hardware.Cameraを使うことで、Android端末にあるカメラデバイスにアクセスして使うことができます。
android.hardware.Cameraは、現在では非推奨となっていますがOpenCV等ではまだまだ使われています。

今回は、カメラプレビューを表示します。
下記の実装はすべて前回までに作ったクラスを修正しています。

カメラプレビューを表示するActivity

前回作ったプロジェクトに新規アクティビティCameraActivityを作ります。
CameraActivityは渡されたカメラIDに対応するカメラのプレビューを初期表示として表示する機能を持つように実装します。
前回作ったMainActivity#addSelectCameraButtonを修正してボタンが押されるとCameraActivityが起動するようにします。
    /**
     * カメラ選択ボタンを作る
     */
    private void addSelectCameraButton() {
        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.linear_layout_camera_select);
        for (final Camera.CameraInfo cameraInfo : mCameraList) {
            Button button = new Button(this);
            button.setGravity(Gravity.LEFT);
            StringBuilder buttonText = new StringBuilder();
            // カメラの設置箇所を判断する
            buttonText.append("カメラの設置箇所 : ");
            if(Camera.CameraInfo.CAMERA_FACING_FRONT == cameraInfo.facing) {
                buttonText.append("フロントカメラ");
            } else {
                buttonText.append("バックカメラ");
            }

            buttonText.append("\n");
            buttonText.append("カメラの向き : ");
            buttonText.append(cameraInfo.orientation);
            buttonText.append("\n");
            buttonText.append("シャッター音 : ");
            if (cameraInfo.canDisableShutterSound) {
                buttonText.append("無効化できます。");
            } else {
                buttonText.append("無効化できません。");
            }
            button.setText(buttonText.toString());
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Intent intent = new Intent(MainActivity.this,CameraActivity.class);
                    intent.putExtra(CameraActivity.CAMERA_ID , mCameraList.indexOf(cameraInfo));
                    startActivity(intent);
                }
            });
            linearLayout.addView(button);
        }
    }

レイアウト

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="yona.mycamera.CameraActivity">

    <FrameLayout
        android:id="@+id/container_camera"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

CameraActivity

プレビューを表示する機能は全てMyCameraPreview(独自のView)に実装する予定なので、 CameraActivityに必要な機能はMyCameraPreviewをインスタンス化してViewツリーに追加します。
また、Activity#onPause内でカメラプレビューを止めたり、リソースの解放処理を行います。
public class CameraActivity extends Activity {

    public static final String CAMERA_ID = "CameraActivity_CAMERA_ID";

    private MyCameraPreview mMyCameraPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        int cameraId = getIntent().getIntExtra(CAMERA_ID, 0);

        mMyCameraPreview = new MyCameraPreview(this , cameraId);
        ((FrameLayout)findViewById(R.id.container_camera)).addView(mMyCameraPreview);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMyCameraPreview.onPause();
    }

カメラプレビューを表示するView

android.hardware.CameraのプレビューはSurfaceViewを使って表示します。
SurfaceViewを継承した独自クラスを作成します。
カメラプレビューを表示するまでには幾つかの手順を踏む必要があります。下記の流れに注意して実装する必要があります。
  1. 適切に初期化された描画可能なSurfaceViewを用意する
  2. カメラデバイスをオープン状態(アプリケーションがカメラデバイスへアクセス権を持つ状態)にし、Cameraインスタンスを取得します。以降はCameraインスタンスを経由してカメラデバイスにアクセスします。
  3. プレビューを開始する
  4. 画面の中断時にカメラデバイスをリリース状態(アプリケーションがカメラデバイスへのアクセス権を解放した状態)にします。再度プレビューを表示するにはカメラデバイスをオープン状態にするところからやり直す必要があります。
プレビューを表示するためには下記のメソッドを使用します。
static Camera open()背面カメラのCameraインスタンスを取得します。背面カメラがない場合はnullが返却されます。
static Camera open(int cameraId)カメラIDを指定してCameraインスタンスを取得します。
final void setPreviewDisplay(SurfaceHolder holder)カメラプレビューを表示するSurfaceViewをSurfaceHolderインスタンスを使って指定します。
final void startPreview()プレビューを開始します。
final void stopPreview()プレビューを停止します。
final void release()Cameraインスタンスに紐づくカメラデバイスを解放します。

MyCameraPreviewのコンストラクタ

MyCameraPreviewのコンストラクタはContextインスタンスと初期表示でプレビューを表示するカメラIDを引数とします。
コンストラクタではCameraインスタンスは作らず、カメラIDをフィールド変数に保存することとSurfaceViewの初期化準備を行います。
    private final static String TAG = "MyCameraPreview";

    private int cameraId;

    private SurfaceHolder mHolder;

    public MyCameraPreview(Context context, int cameraId) {
        super(context);
        this.cameraId = cameraId;
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

カメラプレビューを開始するためメソッドを実装する

カメラプレビューを表示するには下記の手順が必要になります。
  1. Camera.openでCameraインスタンスを取得する。
  2. Camera#setPreviewDisplayに初期化済みのSurfaceViewのSurfaceHolderを渡す。
  3. Camera#startPreviewを呼び出しプレビューを開始します。

    private Camera mCamera;

    private void startPreview() {
        Camera camera = Camera.open(this.cameraId);
        mCamera = camera;
        try {
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.startPreview();
    }

カメラプレビューを停止するためメソッドを実装する

カメラプレビューを一時的に停止するにはCamear#stopPreviewを使います。
このメソッドはあくまでも一時停止なので再開を前提としている場合に使用します。
停止しただけではリソースの解放は行われないので、適切に解放処理を行ってください。
    private void stopPreview(){
        if (mCamera != null) {
            mCamera.stopPreview();
        }
    }

カメラプレビューを開始する

カメラプレビューを開始するには、SurfaceViewの初期化が完了している必要があります。
そのため、SurfaceHolder.Callbackの初期化時に呼ばれるSurfaceHolder.Callback#surfaceCreatedかSurfaceHolder.Callback#surfaceChangedの中でカメラプレビューを開始するメソッドを呼ぶ必要があります。
今回はSurfaceHolder.Callback#surfaceChangedの中で呼ぶことにします。

    public void surfaceCreated(SurfaceHolder holder) {
        mHolder = holder;
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        if (mHolder.getSurface() == null){
            return;
        }
        stopPreview();
        startPreview();
    }

カメラデバイスのリソースを解放する

Cameraインスタンスをオープン状態で放置すると他のアプリケーションがCameraインスタンスを取得できない問題が発生する可能性があります。
そのため、Activity#onPause等でアクティビティがバックグラウンドに回ったときでもCameraインスタンスは解放するようにしましょう。
    public void onPause(){
        if(mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }

まとめ

今回はカメラプレビューを表示する方法を説明しました。
しかし、実際にカメラプレビューを表示すると端末によっては90度回転した状態でプレビューが表示される可能性があります。
次回はこの対応の説明を行おうと思います。

public class MyCameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private final static String TAG = "MyCameraPreview";

    private int cameraId;

    private SurfaceHolder mHolder;

    private Camera mCamera;

    public MyCameraPreview(Context context, int cameraId) {
        super(context);
        this.cameraId = cameraId;
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        mHolder = holder;
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        if (mHolder.getSurface() == null){
            return;
        }
        stopPreview();
        startPreview();
    }

    private void startPreview() {
        Camera camera = Camera.open(this.cameraId);
        mCamera = camera;
        try {
            mCamera.setPreviewDisplay(mHolder);
        } catch (IOException e) {
            e.printStackTrace();
        }
        mCamera.startPreview();
    }

    private void stopPreview(){
        if (mCamera != null) {
            mCamera.stopPreview();
        }
    }

    public void onPause(){
        if(mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }
}