【今更だけどandroid.hardware.Cameraを使う】ビデオを撮る

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

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

Android端末に実装されているカメラデバイスは固有の性能があります。
そのため、ある端末では出来るのに別の端末では出来ないということが多々あります。
今回はCameraとMediaRecorderを組み合わせてビデオを撮影する方法を説明します。
下記の実装はすべて前回までに作ったクラスを修正しています。

ビデオを撮影するには

Cameraを使って撮影するには下記の条件があります。
  1. Camera#startPreviewによってカメラプレビューが開始されていること
  2. ビデオ撮影用に設定されたPrepared状態のMediaRecorderインスタンス
条件1に関してはこれまで説明しているので省略します。今回は条件2と撮影開始、撮影終了について説明します。

MediaRecorderをビデオ撮影用に設定する

MediaRecorderは状態を持つクラスで、状態遷移から外れる行為を行うとすぐに例外が発生します。
そのため、下記の順番を守って各種設定を行ってください。

MediaRecorderのInitial状態にする

MediaRecorderのインスタンス化と状態遷移には関与しない設定を行います。
MediaRecorder#setCameraでビデオ撮影に使用するカメラをCameraインスタンスを使って指定します。
MediaRecorder#setOrientationHintでビデオファイル作成時の向きを指定することができます。
        mediaRecorder = new MediaRecorder();
        // 使用するカメラに対応するCameraインスタンスをセットする。
        mediaRecorder.setCamera(mCamera);
        // カメラデバイスの向きをMediaRecorderに教える。Camera#setDisplayOrientationと同様の値を与える。
        mediaRecorder.setOrientationHint(getCameraDisplayOrientation());

MediaRecorderをInitialized状態にする

MediaRecorderをInitial状態からInitialized状態にするには音源ソースと映像ソースを指定する必要があります。 MediaRecorder#setAudioSourceを使って音源ソースを設定します。
MediaRecorder#setVideoSourceを使って映像ソースを設定します。
        // 音源ソースと映像ソースをそれぞれ設定する
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

MediaRecorderをDataSourceConfigured状態にする

MediaRecorderをInitialized状態からDataSourceConfigured状態にするには各種エンコーダー等のプロファイルする必要があります。
しかし、これらの情報はカメラ固有のプロファイルから取得して設定する必要があります。
カメラのプロファイルはCamcorderProfile.getを使うことで取得することができます。
MediaRecorder#setProfileを使うことで取得したカメラのプロファイルを設定することができます。
MediaRecorder#setOutputFileを使うことで出力するディレクトリやファイル名を指定することができます。
MediaRecorder#setPreviewDisplayを使うことでプレビューに使用するSurfaceを設定することができますが、これはあってもなくても問題ないように思います。
        // カメラデバイスのプロファイルを取得し、MediaRecorderに設定する。
        // MediaRecorderのDataSourceConfigured状態で必要な各種設定が終わります。
        CamcorderProfile camcorderProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
        // 出力ファイルを設定する
        mediaRecorder.setProfile(camcorderProfile);
        mediaRecorder.setOutputFile([ ファイルパス]);
        // プレビューを設定する
        mediaRecorder.setPreviewDisplay(mHolder.getSurface());

MediaRecorderをPrepare状態にする

MediaRecorderをDataSourceConfigured状態からPrepare状態にするにはMediaRecorder#prepareを使います。
このメソッドが正常に終了すると、ビデオ撮影が可能になります。

        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }

ビデオ撮影開始

デフォルトではカメラデバイスへのアクセスはCameraインスタンスがロックを掛けた状態になっています。
ビデオを撮影開始するにはMediaRecorderインスタンスがロックを掛ける必要があります。そのためCamera#unlockを使いカメラデバイスへのアクセスロックを解除します。
その後、先述のMediaRecorderインスタンスの初期化を行い、MediaRecorder#startを呼ぶことでPrepare状態からRecording状態に状態遷移しビデオ撮影が開始されます。


    private MediaRecorder mediaRecorder;

    public void startVideoRecording(){
        mCamera.unlock();
        mediaRecorder = new MediaRecorder();
        // 使用するカメラに対応するCameraインスタンスをセットする。
        mediaRecorder.setCamera(mCamera);
        // カメラデバイスの向きをMediaRecorderに教える。Camera#setDisplayOrientationと同様の値を与える。
        mediaRecorder.setOrientationHint(getCameraDisplayOrientation());
        // 音源ソースと映像ソースをそれぞれ設定する
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        // カメラデバイスのプロファイルを取得し、MediaRecorderに設定する。
        // MediaRecorderのDataSourceConfigured状態で必要な各種設定が終わります。
        CamcorderProfile camcorderProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
        // 出力ファイルを設定する
        mediaRecorder.setProfile(camcorderProfile);
        mediaRecorder.setOutputFile(getOutputMediaFile().toString());
        // プレビューを設定する
        mediaRecorder.setPreviewDisplay(mHolder.getSurface());
        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaRecorder.start();
    }

    private static File getOutputMediaFile(){
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyCameraApp");
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_"+ timeStamp + ".mp4");
        return mediaFile;
    }

ビデオ撮影終了

MediaRecorder#stopを呼ぶことで、ビデオ撮影が終了します。
MediaRecorderインスタンスを再度利用するにはMediaRecorder#resetを使いInitial状態に戻します。
MediaRecorderインスタンスを解放するためにはMediaRecorder#releaseを使います。
この状態のMediaRecorderインスタンスは二度と使用できないため、最初から設定をやり直す必要があります。
また、Camera#reconnect(Camera#lockではないらしい)を使い再度カメラデバイスへロックを掛けます。

    public void stopVideoRecording(){
        if (mediaRecorder != null) {
            mediaRecorder.stop();
            releaseMediaRecorder();
        }
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
//            mCamera.lock();
            try {
                mCamera.reconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

まとめ

CameraとMediaRecorderを組み合わせてビデオを撮影する方法を説明しました。
大抵のカメラアプリでは撮影開始と撮影終了は同じボタンを使うので下記のような実装になると思います。
また、Activity#onPause時にCameraインスタンスだけでなくMediaRecorderインスタンスもリリース処理が必要になるので注意が必要です。

    private boolean isRecording;

    private MediaRecorder mediaRecorder;

    public void toggleVideoRecording(){
        if(isRecording) {
            isRecording = false;
            stopVideoRecording();
        } else {
            isRecording = true;
            startVideoRecording();
        }
    }

    public void startVideoRecording(){
        mCamera.unlock();
        mediaRecorder = new MediaRecorder();
        // 使用するカメラに対応するCameraインスタンスをセットする。
        mediaRecorder.setCamera(mCamera);
        // カメラデバイスの向きをMediaRecorderに教える。Camera#setDisplayOrientationと同様の値を与える。
        mediaRecorder.setOrientationHint(getCameraDisplayOrientation());
        // 音源ソースと映像ソースをそれぞれ設定する
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        // カメラデバイスのプロファイルを取得し、MediaRecorderに設定する。
        // MediaRecorderのDataSourceConfigured状態で必要な各種設定が終わります。
        CamcorderProfile camcorderProfile = CamcorderProfile.get(cameraId, CamcorderProfile.QUALITY_HIGH);
        // 出力ファイルを設定する
        mediaRecorder.setProfile(camcorderProfile);
        mediaRecorder.setOutputFile(getOutputMediaFile().toString());
        // プレビューを設定する
        mediaRecorder.setPreviewDisplay(mHolder.getSurface());
        try {
            mediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mediaRecorder.start();
    }

    public void stopVideoRecording(){
        if (mediaRecorder != null) {
            mediaRecorder.stop();
            releaseMediaRecorder();
        }
    }

    private static File getOutputMediaFile(){
        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyCameraApp");
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File mediaFile = new File(mediaStorageDir.getPath() + File.separator + "VID_"+ timeStamp + ".mp4");
        return mediaFile;
    }

    public void onPause() {
        releaseMediaRecorder();
        releaseCamera();
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();
            mediaRecorder.release();
            mediaRecorder = null;
//            mCamera.lock();
            try {
                mCamera.reconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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