woshidan's loose leaf

ぼんやり勉強しています

回転が難しいというか、3つくらいこけてるとどこでこけてるかわからんね 2

  • 純正のカメラアプリでもPORTRAITだけ対応だったりするので、回転してすぐカメラのプレビューが取れないとかはまあある話なのでは
    • 単一方向で対応してその先はその後考えようか
  • バッファとかの回転のマトリックスは固定値返したりとかあるみたいですね
  • rotateとかtranslateの中心座標指定めっちゃ便利
  • pauseからの復帰で戻ってくると CameraDevice.StateCallback のonErrorでエラーコード 1 = (ERROR_CAMERA_IN_USE) を受け取る
    • このエラーは優先的にCamera APIを利用してるクライアントがありますよ、と言う意味だが、もちろんいまテスト用に書いてるものしか利用してないので、そのクライアントは自分のこととなる
      • ロック、ロックお前なのか…
    • 明日はあのサンプルの仰々しいロックの導入を試して見るかなー

Matrix 中心座標を入れられるの便利

    private Matrix getDirectionAdjustedMatrix() {
        //  Surface.ROTATION_90 == landscape
        Matrix matrix = new Matrix();
        int viewCenterX = (int) (mTextureView.getX() + mTextureView.getWidth() / 2);
        int viewCenterY = (int) (mTextureView.getY() + mTextureView.getHeight() / 2);

        matrix.postRotate(270 /* sensorOrientation などとの兼ね合いから決まる */, viewCenterX, viewCenterY);
        matrix.postScale((1.0f * IMAGE_WIDTH / IMAGE_HEIGHT) * (mTextureView.getWidth() / mTextureView.getHeight()), (1.0f * IMAGE_HEIGHT / IMAGE_WIDTH) * (mTextureView.getWidth() / mTextureView.getHeight()),
                viewCenterX,  viewCenterY);

        return matrix;
    }

onPause -> onResumeの復帰がうまくいかない

    @Override
    public void onPause() {
        if (mCaptureSession != null) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (mImageReader != null) {
            mImageReader.close();
            mImageReader = null;
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        super.onResume();

        // When the screen is turned off and turned back on, the SurfaceTexture is already
        // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
        // a camera and start preview from here (otherwise, we wait until the surface is ready in
        // the SurfaceTextureListener).
        if (mTextureView.isAvailable()) {
            openCamera();
        } else {
            mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                    // 先ほどのカメラを開く部分をメソッド化した
                    openCamera();
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    return true;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }

            });
        }
    }
    public static abstract class StateCallback {
       /**
         * An error code that can be reported by {@link #onError}
         * indicating that the camera device is in use already.
         *
         * <p>
         * This error can be produced when opening the camera fails due to the camera
        *  being used by a higher-priority camera API client.
         * </p>
         *
         * @see #onError
         */
        public static final int ERROR_CAMERA_IN_USE = 1;
  • [既存課題]縦向きの場合と横向きの場合でちょうどいいrotationのMatrixを見つける
  • [休止][既存課題]縦向きの場合と横向きの場合でそれぞれちょうどよくCameraのPreviewが表示されるようにする
  • [新しい課題]Pauseからうまく復帰できない

回転が難しいというか、3つくらいこけてるとどこでこけてるかわからんね

  • Previewを表示するSurfaceの大きさは枠はViewのサイズ、画像の大きさはTextureView.setTransformで渡すMatrixで設定(っぽい)
    • CameraSessionから飛んでくる元画像の大きさはSurfaceTexture.setDefaultBufferSize でここでは向きの調整は入れられないっぽい
    • TextureViewの縦横比は TextureView.setAspectRatio() で設定できる?
  • LANDSCAPE用の回転 + 縮小をかけている場合、POTRAITではカメラのプレビューが表示されない
  • ImageReaderの書き出す画像の向きがLANDSCAPE時に上下正しくなる向き(PORTRAITだと表示に利用する前に回転をかける必要がある)
  • カメラのデバイスがCameraSessionで飛ばしてくる画像が、端末の向きに対してどれくらい回転しているかは CameraCharacteristics.SENSOR_ORIENTATION で取れるが、カメラによって固定っぽい
  • Activityの向いている方向は getWindowManager().getDefaultDisplay().getRotation()

気になってること

  • [新しい課題]縦向きの場合と横向きの場合でちょうどいいrotationのMatrixを見つける
  • [新しい課題]縦向きの場合と横向きの場合でそれぞれちょうどよくCameraのPreviewが表示されるようにする

CameraDevice closeのタイミングとCameraCaptureSession closeのタイミングについて

https://github.com/googlesamples/android-Camera2Basic のサンプルから確認してメモする。

まとめ

  • CameraDeviceCameraDevice.StateCallback のコールバックの中でcloseする
    • onDisconnectedonError の中でclose
    • セマフォのロックを解放しながら処理をしていた。このセマフォのロックは CameraDevice をオープンする時に獲得しようとされている
      • セマフォはカメラを解放する前にアプリの処理が終了するのを防ぐため
        • onPauseで処理をする場合など、onPause -> 次の処理へ行かないようにする働きがある… のか?
        • カメラを解放する前後の処理は一つのスレッドしか処理を進めないようにしている?
  • CameraCaptureSessiononPauseclose していた

コード

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L184-L215

    /**
     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
     */
    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

        @Override
        public void onOpened(@NonNull CameraDevice cameraDevice) {
            // This method is called when the camera is opened.  We start camera preview here.
            mCameraOpenCloseLock.release();
            mCameraDevice = cameraDevice;
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice cameraDevice, int error) {
            mCameraOpenCloseLock.release();
            cameraDevice.close();
            mCameraDevice = null;
            Activity activity = getActivity();
            if (null != activity) {
                activity.finish();
            }
        }

    };

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L456-L461

    @Override
    public void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L624-L647

    /**
     * Closes the current {@link CameraDevice}.
     */
    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

気になってることについては

  • [既存気になること]デバイスの向きとアプリの向きの差分
  • [新しい気になること]Javaセマフォの使い方のイメージがない(並行処理Java読む?)

とりあえずウェブオペレーションが2周目読んでからじゃないと次行っちゃダメっぽい感じで並行処理は来月かなー。

リリースについてのetc.

なんかあったら追加する。

  • Android
    • ベータ版リリースはクローズドベータで指定したメアドのGoogleアカウントでのみインストール、アップデート可能に
    • ベータ版リリースは2回目以降は
      • 更新に~1h程度かかる
      • 同じVersionCodeのものは出せない
        • その辺はアルファ使う?
      • ベータ版から本番へ適用する時のステップはGoogle Play Consoleのアップデートによって変わることがあるが、こないだ試したらベータ版リリース -> 「ROLLOUT TO PRODUCTION」-> 本番へのドラフト追加 -> 本番からリリースだった

アプリで決めたActivityの向きがLANDSCAPEの時、Camera2のCameraDeviceから受け取るカメラのプレビューの向きがずれる

昨日のImageReaderのメモで プレビューの向きがおかしいのが気になる、という話を書きました。

その件について調べると

https://stackoverflow.com/questions/34536798/android-camera2-preview-is-rotated-90deg-while-in-landscape

などどうもアプリの画面の向きがLANDSCAPEの場合、そういうことがあるらしい。

一つには、TextureView に画像変換用の Matrix をセットすることで、TextureViewにセットされる画像をいじることができて、例えば下記のように記述した場合、

        // https://stackoverflow.com/questions/34536798/android-camera2-preview-is-rotated-90deg-while-in-landscape
        Matrix rotate = new Matrix();
        rotate.postScale(0.5f, 0.5f);
        rotate.postTranslate(IMAGE_HEIGHT / 2, 0);
        rotate.postRotate(60);
        mTextureView.setTransform(rotate);

みたいな感じとなる。

これをもうちょっとうまくやる。

        // Landscapeのアプリの場合、カメラの向きとプレビューの向きがずれるっぽい
        // https://stackoverflow.com/questions/34536798/android-camera2-preview-is-rotated-90deg-while-in-landscape
        Matrix rotate = new Matrix();
        rotate.postRotate(270); // デバイスの向きとアプリの向きの差分から決めると良い 
        // 関連: https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L520-L541
        rotate.postTranslate(0, mTextureView.getWidth());
        rotate.postScale(1.0f * 1.15f, 0.56f * 1.15f); // とりあえず頑張ってうめた
        mTextureView.setTransform(rotate);

これでだいたいプレビューの向きとサイズがあってくる。

このMatrixでの操作手順は、前操作した結果にさらに次の操作が反映されるので実際紙などを使って考えた方が早い。

さて、さっきのコードについてScaleが手打ちだとちょっと困るのでもう少しだけ頑張って

    private void createCameraPreviewSession() {
        // Landscapeのアプリの場合、カメラの向きとプレビューの向きがずれるっぽい
        // https://stackoverflow.com/questions/34536798/android-camera2-preview-is-rotated-90deg-while-in-landscape
        Matrix rotate = new Matrix();
        rotate.postRotate(270); // デバイスの向きとアプリの向きの差分から決めると良い
        // 関連: https://github.com/googlesamples/android-Camera2Basic/blob/master/Application/src/main/java/com/example/android/camera2basic/Camera2BasicFragment.java#L520-L541
        rotate.postTranslate(0, mTextureView.getWidth());
        rotate.postScale((1.0f * IMAGE_WIDTH / IMAGE_HEIGHT) * (mTextureView.getWidth() / mTextureView.getHeight()), (1.0f * IMAGE_HEIGHT / IMAGE_WIDTH) * (mTextureView.getWidth() / mTextureView.getHeight()));
        mTextureView.setTransform(rotate);
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        texture.setDefaultBufferSize(IMAGE_WIDTH, IMAGE_HEIGHT); // 自分の手元のデバイスで決めうちしてます

こう。

今日時点での全体のコードは https://gist.github.com/woshidan/5443e4d0d779ffff036862d7010e14ef

気になってることについては

  • [新しい気になること]デバイスの向きとアプリの向きの差分
  • [既存気になること]CameraDevice解放のタイミングについて
  • [既存気になること]CameraCaptureSession closeのタイミングについて

View AnimationとProperty Animationの違いについて

  • View Animation
    • Animationクラスのサブクラス
    • Viewの見た目の特徴のうち一つをいじるアニメーションのクラス
    • 具体例:
      • TranslateAnimation
      • ScaleAnimation
      • RotateAnimation
      • AlphaAnimation
    • 変化するのはrenderの結果だけで本当の値は変化していない(出典: https://developer.android.com/guide/topics/graphics/prop-animation.html#property-vs-view )
    • テスト用アプリでView Animationのボタンを複数押してもそれらの結果は同時に適用されない
      • それぞれのアニメーション前後でViewの状態が変化していないから、それぞれアニメーション実行前の状態から変化させた結果になる
    • ボタンを押す前後で見た目は変わってもViewのプロパティを調べるログの結果は変化しない
  • Property Animation
    • Viewに生えているメソッド
    • Viewのプロパティを変更するアニメーションのクラス
    • 具体例:
      • View.setTranslationX/Y
      • View.setScaleX/Y
      • View.setRotation, setPivotX/Y
      • View.setAlpha
    • Viewのプロパティを変更している
    • テスト用アプリでProperty Animationのボタンを複数押すとそれらの結果は同時に適用される
    • ボタンを押す前後で見た目は変わってもViewのプロパティを調べるログの結果は変化する

テスト用アプリコードはこちら View Animation vs Property Animation · GitHub

アニメーション: テスト用アプリでView Animationのボタンを複数押してもそれらの結果は同時に適用されない

アニメーション: テスト用アプリでProperty Animationのボタンを複数押すとそれらの結果は同時に適用される

参考

developer.android.com

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

参考