Androidのカメラアプリを作ってみようと思ったのだが Nexus7 にはフロントカメラしか無い。orz
QRコード・リーダーぐらいならなんとかなるかと思い手を付けてみた。

カメラの準備

普通は以下のようにするとカメラが準備できるのだが Nexus7 だとリアカメラを開こうとして失敗する。

Camera camera = Camera.open();

Nexus7 のフロントカメラを開くにはこうする。

int cameraId = 0;
Camera camera = Camera.open(cameraId);

cameraId はハードのカメラの実装状況によりまちまちなので正しくはこんなことする必要が有りそう。

Camera.CameraInfo info = new Camera.CameraInfo();
for (int id=0; id<Camera.getNumberOfCameras(); id++) {
    Camera.getCameraInfo(id, info);
    if (info.??? == ???) { // 使いたいカメラの条件式
        cameraId = id;
    }
}

AnsroidManufest.xmlにカメラのパーミッションも必要。

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="true"/>
<!--
<uses-feature android:name="android.hardware.camera.front" android:required="true"/>
-->

android.hardware.camera.front は無くても良いらしい...

プレビュー

SurfaceView を使う。 ググれば解説多数なので割愛。

QRコードのライブラリ

QRコードのライブラリは ZXing を使う。 google が開発しオープン化されている物。

ここから落せる。

しかし、現行の ZXing-2.2.zip にはソースしか入っていないので以下から2.3-SNAPのjarを落してきた。

  • https://oss.sonatype.org/content/repositories/snapshots/com/google/zxing/

必要なものは core-xxxxx.jar と javase-xxxxxx.jar のみ。
eclipse の Android プロジェクトの libs に入れておけば利用可能。

使い方はサンプル・コードの onPreviewFrame() 内で完結している。

注意するのはフロントカメラは左右反転しているので PlanarYUVLuminanceSource コンストラクタの最後の引数が ture になっている必要がある。

サンプル・コード

このソースだけ。リソースも参照していない。

package org.kotemaru.sample.camera;

import java.io.IOException;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Reader;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;

import android.app.Activity;
import android.content.res.Configuration;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.widget.Toast;

public class MainActivity extends Activity {
    private static final String TAG = "CameraSample";

    private int CAMERA_ID = 0; // for Nexus7

    private Camera camera;
    private SurfaceView surfaceView;
    private CameraListener cameraListener = new CameraListener();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        surfaceView = new SurfaceView(this);
        setContentView(surfaceView);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        SurfaceHolder holder = surfaceView.getHolder();
        holder.addCallback(cameraListener);
    }

    private int getOrientation() {
        return getResources().getConfiguration().orientation;
    }

    private class CameraListener implements
            SurfaceHolder.Callback,
            AutoFocusCallback,
            Camera.PictureCallback,
            Camera.PreviewCallback
    {

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            camera = Camera.open(CAMERA_ID);
            try {
                camera.setPreviewDisplay(holder);
            } catch (IOException e) {
                Log.e(TAG, e.toString(), e);
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format,
                int width, int height) {

            // カメラのプレビューサイズをViewに設定
            Camera.Parameters parameters = camera.getParameters();
            Camera.Size size = parameters.getSupportedPreviewSizes().get(0); // 0=最大サイズ 
            parameters.setPreviewSize(size.width, size.height);
            camera.setParameters(parameters);

            // 画面回転補正。
            ViewGroup.LayoutParams layoutParams = surfaceView.getLayoutParams();
            if (getOrientation() == Configuration.ORIENTATION_PORTRAIT) {
                camera.setDisplayOrientation(90);
                layoutParams.width = size.height;
                layoutParams.height = size.width;
            } else {
                camera.setDisplayOrientation(0);
                layoutParams.width = size.width;
                layoutParams.height = size.height;
            }
            surfaceView.setLayoutParams(layoutParams);

            // オートフォーカス設定。
            camera.autoFocus(cameraListener);

            camera.startPreview();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            camera.autoFocus(null);
            camera.setPreviewCallback(null);
            camera.release();
            camera = null;
        }

        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            if (success) {
                Log.d(TAG, "focus");
                // プレビューのデータ取得。
                camera.setPreviewCallback(cameraListener);
                // フルサイズ画像はTODO
                //camera.takePicture(null,null,cameraListener);
            }
        }

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            // フルサイズ画像もやることは同じ。
            onPreviewFrame(data, camera);
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            // 処理は1回なのでコールバック取り消し
            camera.setPreviewCallback(null);

            // 基礎データ取得
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(CAMERA_ID, info);
            int w = camera.getParameters().getPreviewSize().width;
            int h = camera.getParameters().getPreviewSize().height;
            //int w = camera.getParameters().getPictureSize().width;
            //int h = camera.getParameters().getPictureSize().height;
            boolean isMirror = (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT);

            // プレビュー画像の型変換
            PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
                    data, w, h, 0, 0, w, h, isMirror);
            BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));

            // QRコード読み込み。
            Reader reader = new MultiFormatReader();
            try {
                Log.d(TAG, "decode");
                Result result = reader.decode(bitmap);
                String text = result.getText();

                Toast.makeText(MainActivity.this, text, Toast.LENGTH_LONG).show();
                Log.i(TAG, "result:" + text);
                camera.stopPreview();
                camera.autoFocus(null);
            } catch (Exception e) {
                // QRコード認識失敗でも例外発生する。
                Log.d(TAG, "decode-fail:" + e.toString());
                camera.autoFocus(cameraListener);
            }
        }
    };
}

実行結果

さすがにエミュレータでは動かないので実機で確認。

一応、QRコードを認識する事には成功した。 但し、QRコード自体をかなり拡大しないと認識できない。 小さいとカメラを非常に近づける必要があるためピントが合わないのかもしれない。

それ以前にプレビューを見るのに横からのぞき込まないといけないと言う致命的欠陥が...
(最初から分かっていた訳だがw)

技術的に可能だけど使い物にならないと言う予想通りの結論に達したのでこれで終りにしよう。



参考にさせて頂いたサイト: