ページ

2014/06/24

[Android]Camera#addCallbackBuffer と Camera#setPreviewCallbackWithBuffer を使う

Camera#addCallbackBuffer(byte[])
Camera#setPreviewCallbackWithBuffer(Camera.PreviewCallback)ですが、
使い方がわかりにくかったのでメモしておきます。

まず準備として以下の記述を消します。
自分で Canvas に描画するので必要がないのですね。
mCamera.setPreviewDisplay(holder);

そして全体は以下のような感じで実装します。
private Camera mCamera;
private int mPictureOrientation; // デバイスの向きから計算した写真の向き
private Camera.Size mPreviewSize; // Preview サイズ
private Camera.Size mScaledPreviewSize; // Preview サイズを縦横比を維持しつつ画面サイズに拡大したサイズ
private byte[] mCallbackBuffer;

private void startPreview(){
    int size = mPreviewSize.width * mPreviewSize.height * ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat()) / 8;
    mCallbackBuffer = new byte[size];
    mCamera.addCallbackBuffer(mCallbackBuffer);
    mCamera.setPreviewCallbackWithBuffer(this);
}

private void stopPreview(){
    mCamera.setPreviewCallbackWithBuffer(null);
}

@Override
public void onPreviewFrame(byte[] data, Camera camera){
    // 画像変換処理
    Bitmap bitmap = BitmapUtils.createBitmapFromYuv(mContext, data, mPreviewSize.width, mPreviewSize.height);
    bitmap = BitmapUtils.rotate(bitmap, mPictureOrientation);
    bitmap = BitmapUtils.resize(bitmap, mScaledPreviewSize.width, mScaledPreviewSize.height);
    // 描画処理
    Canvas canvas = mHolder.lockCanvas();
    if(canvas != null){
        canvas.drawBitmap(bitmap, 0, 0, null);
        mHolder.unlockCanvasAndPost(canvas);
    }
    // 次のフレームのコールバックの設定
    mCamera.addCallbackBuffer(mCallbackBuffer);
}
BitmapUtils.createBitmapFromYuv などは wada811/AndroidLibrary@wada811からです。
本当は Bitmap を毎フレーム生成するのは嫌なんですが
YUV データを Bitmap#setPixels を通して Bitmap に変換する方法がよくわからず…。
JavaFilter#decodeYUV420SP · youten/YUV420SP を使えばいけるかもしれない。
youten さんが書かれている通り、速度は出ないので RenderScript を使いたいところ。

やってみた

private Camera mCamera;
private int mPictureOrientation; // デバイスの向きから計算した写真の向き
private Camera.Size mPreviewSize; // Preview サイズ
private Camera.Size mScaledPreviewSize; // Preview サイズを縦横比を維持しつつ画面サイズに拡大したサイズ
private byte[] mCallbackBuffer;
private int[] mRgbDatas;
private Bitmap mPreviewBitmap;

private void startPreview(){
    mRgbDatas = new int[mPreviewSize.width * mPreviewSize.height];
    mPreviewBitmap = BitmapUtils.createNewBitmap(mPreviewSize.width, mPreviewSize.height);
    int size = mPreviewSize.width * mPreviewSize.height * ImageFormat.getBitsPerPixel(mCamera.getParameters().getPreviewFormat()) / 8;
    mCallbackBuffer = new byte[size];
    mCamera.addCallbackBuffer(mCallbackBuffer);
    mCamera.setPreviewCallbackWithBuffer(this);
}

private void stopPreview(){
    mCamera.setPreviewCallbackWithBuffer(null);
}

@Override
public void onPreviewFrame(byte[] data, Camera camera){
    // 画像変換処理
    BitmapUtils.decodeYUV420SP(mRgbDatas, data, mPreviewSize.width, mPreviewSize.height);
    mPreviewBitmap.setPixels(mRgbDatas, 0, mPreviewSize.width, 0, 0, mPreviewSize.width, mPreviewSize.height);
    Bitmap bitmap = BitmapUtils.rotate(mPreviewBitmap, mPictureOrientation);
    bitmap = BitmapUtils.resize(bitmap, mScaledPreviewSize.width, mScaledPreviewSize.height);
    // 描画処理
    Canvas canvas = mHolder.lockCanvas();
    if(canvas != null){
        canvas.drawBitmap(bitmap, 0, 0, null);
        mHolder.unlockCanvasAndPost(canvas);
    }
    // 次のフレームのコールバックの設定
    mCamera.addCallbackBuffer(mCallbackBuffer);
}
BitmapUtils#decodeYUV420SP も遅かったですが、
BitmapUtils#rotate と BitmapUtils#resize で Bitmap の生成をやってしまっているので更に遅かったです。

参考

#13 やってみよう画像処理『カメラのbyte列をいじる』(Androidで) | tech.kayac.com - KAYAC engineers' blog