woshidan's loose leaf

ぼんやり勉強しています

ImageReaderクラスを触ってみた

今日はまとめる余裕がないのでこっちにおくんじゃ。

  • ImageReaderはAPI19で追加された、他のSurface(入力元=カメラなど)から画像を読み取る & 読み取ったコールバックで加工して他の部分へ流す(たとえばBitmapにしてImageViewに渡す)という使い方ができるSurface
    • ImageはImageReaderがonImageAvailableコールバックが呼び出された時に持ってる、画像のバイト列などが扱えるオブジェクト
      • MaxImagesを見ていると、何枚分かデータが貯められるそうだが、よくわからず
      • 生のbyte列を扱えるが、Bitmapと違いUIへ渡せない
    • 今回は http://woshidan.hatenablog.com/entry/2017/09/06/083000 をもとにして https://developer.android.com/things/training/doorbell/camera-input.html を参考にプレビューを見ながらシャッターを押したらさっき撮った画像が小窓に表示される、みたいなのを書いた
  • 追加でCamera2 APIについて気づいたこと
    • mCameraDevice.createCaptureSession にはそのセッションで出力を送りうるすべてのSurfaceを配列で渡す
    • このタイプのリクエストの結果はこのデバイスに… というのは、 CaptureRequest.Builder.addTarget で設定
    • すべてのSurfaceで画面サイズが適切に設定されていないとセッションが開始されないっぽい
      • 新しく追加した ImageReaderSurface の設定がおかしかった時のエラーが以下
        • Surface with size (w=180, h=180) and format 0x21 is not valid, size not in valid set: [5248x3936, 5248x2952, 3840x2160, 3264x2448, 2048x1536, 1920x1080, 1280x720, 640x480, 480x320, 320x240]
        • CameraDevice-JV-0: Stream configuration failed
  • 追加でもんにゃりしていること
    • Preview画面の向きについて(一番下を見ればわかるが、ずれた)
    • CameraDevice解放のタイミングについて
    • CameraCaptureSession closeのタイミングについて

こちらはコード全体のgist ImageReader初見メモ · GitHub

メモがてらのコメント付きコード断片。

        // ImageReaderインスタンスの初期化
        // MAX_IMAGES個だけ同時にImageオブジェクトが取得できる
        mImageReader = ImageReader.newInstance(IMAGE_WIDTH, IMAGE_HEIGHT, ImageFormat.JPEG, MAX_IMAGES);

        // imageAvailableListener -> 画像準備できた時反応するくん
        // backgroundHandler -> Listenerが実行された時に呼び出されるHandler
        // (に対応したLooperって形で処理を実行するバックグラウンドスレッドの指定)
        mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
    private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        // Surfaceから画像が利用できるようになった時に呼び出される
        @Override
        public void onImageAvailable(ImageReader reader) {
            // Imageは各種コーデック(圧縮方法みたいなもの)で圧縮したりする、画像のByteBufferを扱うためのオブジェクト
            Image image = reader.acquireLatestImage();
            // 何枚か画像を扱うことができて(?)、それぞれはPlanesに入っている
            // この辺のコードは https://developer.android.com/things/training/doorbell/camera-input.html のサンプルより
            ByteBuffer imageBuf = image.getPlanes()[0].getBuffer();
            final byte[] imageBytes = new byte[imageBuf.remaining()];
            imageBuf.get(imageBytes);
            image.close();

            final Bitmap bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);

            MainActivity.this.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ImageView imageView = (ImageView) findViewById(R.id.picture);
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    };
    private void copyPreview() {
        // ImageViewへ静止画を送るためのCaptureRequestを作る
        // 静止画を送ってもらうためのリクエストのビルダーですよ
        CaptureRequest.Builder copyPreviewRequestBuilder = null;
        try {
            copyPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        // 送り先はImageReaderにしてね
        copyPreviewRequestBuilder.addTarget(mImageReader.getSurface());
        CaptureRequest copyPreviewRequest = copyPreviewRequestBuilder.build();

        // (プレビュー時にセッションは開いたままで、)追加で静止画送ってくれリクエストを送る
        try {
            mCaptureSession.capture(copyPreviewRequest, null, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

画面は以下のような感じ。

f:id:woshidan:20170910022201p:plainf:id:woshidan:20170910022212p:plain

参考