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

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

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

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

Camera#takePicture

Camera#startPreviewを使いカメラプレビューを表示している状態でCamera#takePictureを使うことで、カメラデバイスからデータを取得することができます。
Camera#takePictureには下記のオーバーロードが存在します。
このメソッドは多数のコールバックを引数としています。
これらのコールバックはキャプチャから徐々に進行していく各データの加工を知らせます。
  1. 画像のキャプチャを取得
  2. キャプチャしたデータをRAW形式に加工
  3. データをスケーリングした状態に加工
  4. データをJPEG形式に
アプリケーションで特定のコールバックが必要ない場合は、コールバックメソッドの代わりにnullを渡すことができます。
final void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback postview, Camera.PictureCallback jpeg) カメラデバイスが画像をキャプチャした後に行われる、各データ加工の完了を通知するコールバックインターフェースを多数必要とします。
しかし、端末によっては対応していない加工もあるため、すべてが必ずコールバックされないことに注意が必要です。
また、必要の無いコールバックにはnullを設定することができます。
Camera.ShutterCallback shutter画像がキャプチャされた後に発生します。
Camera.PictureCallback raw RAWイメージデータが使用可能な場合に発生します。RAWイメージコールバックバッファが使用できない場合、またはRAWイメージコールバックバッファがRAWイメージを保持するのに十分な大きさでない場合、データはnullになります。
Camera.PictureCallback postviewスケーリングされた、完全に処理されたポストビュー画像が利用可能な場合に発生します。すべてのハードウェアがこれをサポートしているわけではありません。
Camera.PictureCallback jpeg JPEGコールバックは、圧縮されたイメージが利用可能な場合に発生します。
final void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg) カメラデバイスが画像をキャプチャした後に行われる、各データ加工の完了を通知するコールバックインターフェースを多数必要とします。
しかし、端末によっては対応していない加工もあるため、すべてが必ずコールバックされないことに注意が必要です。
また、必要の無いコールバックにはnullを設定することができます。
Camera.ShutterCallback shutter画像がキャプチャされた後に発生します。
Camera.PictureCallback raw RAWイメージデータが使用可能な場合に発生します。RAWイメージコールバックバッファが使用できない場合、またはRAWイメージコールバックバッファがRAWイメージを保持するのに十分な大きさでない場合、データはnullになります。
Camera.PictureCallback jpeg JPEGコールバックは、圧縮されたイメージが利用可能な場合に発生します。
Camera#takePictureの処理は非同期で進行します。
そのため、Camera#takePictureの呼び出した後、すぐにCameraクラスのメソッドを使用するのはお勧めしません。
また、呼び出し後にすぐにカメラプレビューは停止されます。
しかし、前述の理由からCamera#startPreviewをすぐには呼び出してはいけません。
今回は、PEGのデータへの操作が完了したタイミングでCamera#startPreviewを呼び出すようにしました。

    private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
        @Override
        public void onShutter() {
            Log.d(TAG, "onShutter: ");
        }
    };
    private Camera.PictureCallback mRawPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.d(TAG, "Raw onPictureTaken: ");
            if (data != null) {
                saveTakenData(data , RAW_EXTENSION);
            }
        }
    };

    private Camera.PictureCallback mPostViewPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.d(TAG, "PostView onPictureTaken: ");
            if (data != null) {
                saveTakenData(data , JPG_EXTENSION);
            }
        }
    };
    
    private Camera.PictureCallback mJpegPictureCallback = new Camera.PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            Log.d(TAG, "Jpeg onPictureTaken: ");
            if (data != null) {
                saveTakenData(data , JPG_EXTENSION);
            }
            startPreview();
        }
    };

    public void takePicture(){
        mCamera.takePicture(mShutterCallback, mRawPictureCallback , mPostViewPictureCallback , mJpegPictureCallback);
    }

Camera.PictureCallbackのデータを保存するメソッド

Camera.PictureCallback#onPictureTakenで得たデータは加工済みのデータになります。
また、カメラプレビューの向きを補正しましたが、Camera.PictureCallback#onPictureTakenで得られるデータには関係がなく、Camera.Parameters#setRotationで向き補正する必要があります。
今回はアプリケーションの内部フォルダに”yyyyMMdd_HHmmss.jpg”または”yyyyMMdd_HHmmss.raw”という形式で保存します。
    private void saveTakenData(byte[] data, String extension) {
        SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String fileName = date.format(Calendar.getInstance().getTime()) + "." + extension;
        FileOutputStream fos = null;
        try {
            fos = getContext().openFileOutput(fileName, Context.MODE_APPEND);
            fos.write(data);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            Log.d("Camera", "Done:" + fileName);
        }
    }

写真を撮るボタン

CameraActivityに写真を撮影するボタンを追加します。

レイアウト

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    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"/>

    <Button
        android:id="@+id/button_take_picture"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="撮影"/>

</RelativeLayout>

Activity

public class CameraActivity extends AppCompatActivity {

    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);

        findViewById(R.id.button_take_picture).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMyCameraPreview.takePicture();
            }
        });
    }

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