ご存知のように、RxJavaは、イベントストリームの処理と非同期メソッドの操作という2つの問題を解決するのに理想的です。 以前の投稿で 、センサーからのイベントのフローを処理するオペレーターのチェーンを構築する方法を示しました。 今日は、RxJavaを使用して実質的に非同期APIを操作する方法を示したいと思います。 このようなAPIとしてCamera2 APIを選択しました。
Camera2 APIの使用例は、まだかなり文書化されておらず、コミュニティによって研究されていますが、以下に示します。 それを飼いならすために、RxJava2が使用されます。 この人気のあるライブラリの2番目のバージョンは比較的最近リリースされ、その例もほとんどありません。
この投稿の対象者 読者は賢明な体験でありながら、好奇心itive盛なAndroid開発者であることを期待しています。 リアクティブプログラミングの基本的な知識( ここでは良い紹介です )とマーブルダイアグラムの理解が非常に望ましいです。 この投稿は、プロジェクトでCamera2 APIを使用したい人だけでなく、事後対応的なアプローチを取りたい人にも役立ちます。 私はあなたに警告します、たくさんのコードがあるでしょう!
プロジェクトのソースはGitHubにあります 。
プロジェクトの準備
プロジェクトにサードパーティの依存関係を追加します。
レトロラムダ
RxJavaを使用する場合、ラムダサポートが絶対に必要です。そうしないと、コードがひどく見えます。 したがって、まだAndroid Studio 3.0に切り替えていない場合は、Retrolambdaをプロジェクトに追加してください。
buildscript { dependencies { classpath 'me.tatarka:gradle-retrolambda:3.6.0' } } apply plugin: 'me.tatarka.retrolambda'
これで、言語バージョンを8に上げて、ラムダのサポートを提供できます。
android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
Rxjava2
compile 'io.reactivex.rxjava2:rxjava:2.1.0'
現在のバージョン、完全な手順とドキュメントはこちらをご覧ください 。
Rxandroid
AndroidでRxJavaを使用するときに便利なライブラリ。 主にAndroidSchedulersに使用されます。 リポジトリ 。
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
Camera2 API
かつて、Camera1 APIを使用して記述されたモジュールのコードレビューに参加しましたが、同時実行性問題APIの避けられない設計に不愉快な驚きを覚えました。 どうやら、Googleも問題を認識し、APIの最初のバージョンを修正しました。 代わりに、Camera2 APIを使用することをお勧めします。 2番目のバージョンは、Android Lollipop以降で利用できます。
彼を見てみましょう。
第一印象
Googleはバグを合理化するのに良い仕事をしました。 すべての操作は非同期で実行され、コールバックを介して結果を通知します。 さらに、対応するハンドラーを渡すと、コールバックメソッドが呼び出されるストリームを選択できます。
リファレンス実装
Googleは、 Camera2Basicアプリケーションの例を提供しています。
これはかなり単純な実装ですが、APIの使用を開始するのに役立ちます。 リアクティブアプローチを使用して、よりエレガントな決定を下せるかどうかを見てみましょう。
スナップショットを撮る手順
つまり、スナップショットを取得するためのアクションのシーケンスは次のとおりです。
- デバイスを選択してください
- デバイスを開きます
- セッションを開く
- プレビューを実行
- ボタンを押して写真を撮ることにより、
- セッションを閉じる
- デバイスを閉じます。
デバイス選択
まず、 CameraManagerが必要です。
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
このクラスを使用すると、システムに存在するカメラに関する情報を取得して接続できます。 複数のカメラが存在する場合がありますが、通常はスマートフォンに2台あります。前面と背面です。
カメラのリストを取得します。
String[] cameraIdList = mCameraManager.getCameraIdList();
それはとても厳しい-文字列識別子のリストです。
次に、各カメラの特性のリストを取得します。
for (String cameraId : cameraIdList) { CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId); ... }
CameraCharacteristicsには、カメラに関する情報を取得できる膨大な数のキーが含まれています。
ほとんどの場合、カメラを選択する段階で、カメラが向けられている場所を見ます。 これを行うには、キーCameraCharacteristics.LENS_FACING
によって値を取得する必要があります。
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
カメラは、正面( CameraCharacteristics.LENS_FACING_FRONT
)、背面( CameraCharacteristics.LENS_FACING_BACK
)、または接続( CameraCharacteristics.LENS_FACING_EXTERNAL
)のいずれかです。
向き設定カメラの選択は次のようになります。
@Nullable private static String getCameraWithFacing(@NonNull CameraManager manager, int lensFacing) throws CameraAccessException { String possibleCandidate = null; String[] cameraIdList = manager.getCameraIdList(); if (cameraIdList.length == 0) { return null; } for (String cameraId : cameraIdList) { CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { continue; } Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); if (facing != null && facing == lensFacing) { return cameraId; } //just in case device don't have any camera with given facing possibleCandidate = cameraId; } if (possibleCandidate != null) { return possibleCandidate; } return cameraIdList[0]; }
これで、目的の方向のカメラID(または、見つからなかった場合は他のID)ができました。 これまでのところ、すべては非常にシンプルで、非同期アクションはありません。
オブザーバブルを作成する
非同期APIメソッドにアプローチします。 create
メソッドを使用して、それぞれをObservableに変換しcreate
。
openCamera
使用する前に、 CameraManager.openCameraメソッドを使用してデバイスを開く必要があります。
void openCamera (String cameraId, CameraDevice.StateCallback callback, Handler handler)
このメソッドでは、選択したカメラのID、非同期結果を取得するコールバック、およびこのハンドラーのストリームでコールバックメソッドを呼び出す場合はハンドラーを渡します。
そこで、最初の非同期メソッドに出会いました。 デバイスの初期化は長くて高価なプロセスであるため、理解できます。
CameraDevice.StateCallback
見てみましょう。
ジェットの世界では、これらの方法はイベントに関連します。 カメラAPI onOpened
、 onClosed
、 onDisconnected
onOpened
ときにイベントを生成するObservableを作成しましょう。 これらのイベントを区別できるように、enumを作成します。
public enum DeviceStateEvents { ON_OPENED, ON_CLOSED, ON_DISCONNECTED }
そして、ジェットストリーム(以降、ジェットストリームを一連のジェットオペレーターと呼びます-スレッドと混同しないでください)でデバイスで何かできるように、生成されたイベントにCameraDevice
へのリンクを追加します。 最も簡単な方法は、 Pair<DeviceStateEvents, CameraDevice>
を生成することです。 Observable
を作成するには、 create
メソッドを使用しcreate
(RxJava2を使用しているので、これを行うことを恥ずかしく思いません)。
create
メソッドのシグネチャは次のとおりです。
public static <T> Observable<T> create(ObservableOnSubscribe<T> source)
つまり、 ObservableOnSubscribe<T>
インターフェイスを実装するオブジェクトをそれに渡す必要があります。 このインターフェイスにはメソッドが1つだけ含まれています。
void subscribe(@NonNull ObservableEmitter<T> e) throws Exception;
Observer
Observable
サブスクライブするたびに呼び出されます。
ObservableEmitter
何であるかを見てみましょう。
public interface ObservableEmitter<T> extends Emitter<T> { void setDisposable(@Nullable Disposable d); void setCancellable(@Nullable Cancellable c); boolean isDisposed(); ObservableEmitter<T> serialize(); }
もういい setDisposable/setCancellable
を使用して、 Observable
からサブスクライブを解除したときに実行されるアクションを指定できます。 Observable
作成するときに、閉じる必要があるリソースを開いた場合、これは非常に便利です。 onClosed
デバイスを閉じるDisposable
を作成できますが、 onClosed
イベントに応答したいので、これを行いません。
isDisposed
メソッドを使用isDisposed
と、他の誰かがObservableにサブスクライブしているかどうかを確認できisDisposed
。
ObservableEmitter
はEmitter
インターフェースを拡張することに注意してください。
public interface Emitter<T> { void onNext(@NonNull T value); void onError(@NonNull Throwable error); void onComplete(); }
これらが必要な方法です! CameraAPIがCameraDeviceインターフェイスコールバックを呼び出すたびに、onNextを呼び出します onOpened
/ onClosed
/ onDisconnected
; Camera APIがonError
呼び出すと、 onError
。
したがって、私たちは知識を適用します。 Observable
を作成するメソッドは次のようになります(読みやすくするために、 isDisposed()
チェックを削除しましたisDisposed()
退屈なチェックの完全なコードを参照してください)。
public static Observable<Pair<DeviceStateEvents, CameraDevice>> openCamera( @NonNull String cameraId, @NonNull CameraManager cameraManager ) { return Observable.create(observableEmitter -> { cameraManager.openCamera(cameraId, new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { observableEmitter.onNext(new Pair<>(DeviceStateEvents.ON_OPENED, cameraDevice)); } @Override public void onClosed(@NonNull CameraDevice cameraDevice) { observableEmitter.onNext(new Pair<>(DeviceStateEvents.ON_CLOSED, cameraDevice)); observableEmitter.onComplete(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { observableEmitter.onNext(new Pair<>(DeviceStateEvents.ON_DISCONNECTED, cameraDevice)); observableEmitter.onComplete(); } @Override public void onError(@NonNull CameraDevice camera, int error) { observableEmitter.onError(new OpenCameraException(OpenCameraException.Reason.getReason(error))); } }, null); }); }
いいね! もう少し反応が良くなりました!
前述したように、すべてのCamera2 APIメソッドはパラメーターの1つとしてHandler
を受け入れます。 null
を渡すと、現在のスレッドでコールバック呼び出しを受け取ります。 私たちの場合、これはsubscribe
が呼び出されたスレッド、つまりメインスレッドです。
createCaptureSession
CameraDevice
ができたCameraDevice
、 CaptureSession
を開くことができます。 しないでください!
これを行うには、 CameraDevice.createCaptureSessionメソッドを使用します。 彼の署名は次のとおりです。
public abstract void createCaptureSession(@NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler) throws CameraAccessException;
Surface
のリスト(取得する場所については後で説明します)とCameraCaptureSession.StateCallback
。 その中にどんなメソッドがあるのか見てみましょう。
リッチ! しかし、コルベックを倒す方法はすでに知っています。 Camera APIがこれらのメソッドを呼び出すときにイベントを生成するObservable
を作成します。 それらを区別するには、enumを作成します。
public enum CaptureSessionStateEvents { ON_CONFIGURED, ON_READY, ON_ACTIVE, ON_CLOSED, ON_SURFACE_PREPARED }
そして、リアクティブストリームにCameraCaptureSession
オブジェクトがあるように、 CaptureSessionStateEvent
だけでなく、 Pair<CaptureSessionStateEvents, CameraCaptureSession>
ます。 このようなObservable
を作成するメソッドのコードは次のようになります(読みやすくするためにチェックは再び削除されます)。
@NonNull public static Observable<Pair<CaptureSessionStateEvents, CameraCaptureSession>> createCaptureSession( @NonNull CameraDevice cameraDevice, @NonNull List<Surface> surfaceList ) { return Observable.create(observableEmitter -> { cameraDevice.createCaptureSession(surfaceList, new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { observableEmitter.onNext(new Pair<>(CaptureSessionStateEvents.ON_CONFIGURED, session)); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { observableEmitter.onError(new CreateCaptureSessionException(session)); } @Override public void onReady(@NonNull CameraCaptureSession session) { observableEmitter.onNext(new Pair<>(CaptureSessionStateEvents.ON_READY, session)); } @Override public void onActive(@NonNull CameraCaptureSession session) { observableEmitter.onNext(new Pair<>(CaptureSessionStateEvents.ON_ACTIVE, session)); } @Override public void onClosed(@NonNull CameraCaptureSession session) { observableEmitter.onNext(new Pair<>(CaptureSessionStateEvents.ON_CLOSED, session)); observableEmitter.onComplete(); } @Override public void onSurfacePrepared(@NonNull CameraCaptureSession session, @NonNull Surface surface) { observableEmitter.onNext(new Pair<>(CaptureSessionStateEvents.ON_SURFACE_PREPARED, session)); } }, null); }); }
setRepeatingRequest
カメラからのライブ画像を画面に表示するには、デバイスから常に新しい画像を受信し、表示用に転送する必要があります。 このため、APIには便利なCameraCaptureSession.setRepeatingRequestメソッドがあります。
int setRepeatingRequest(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) throws CameraAccessException;
この操作をリアクティブにするために、既知の手法を使用します。 CameraCaptureSession.CaptureCallbackインターフェイスを確認します。
繰り返しますが、生成されたイベントを区別し、このためにenum
を作成します。
public enum CaptureSessionEvents { ON_STARTED, ON_PROGRESSED, ON_COMPLETED, ON_SEQUENCE_COMPLETED, ON_SEQUENCE_ABORTED }
CameraCaptureSession
、 CaptureRequest
、 CaptureResult
など、多くの情報がリアクティブストリームに含めるCameraCaptureSession
にCaptureRequest
ため、 Pair<>
だけでは機能しなくなりましたCaptureResult
作成しましょう。
public static class CaptureSessionData { final CaptureSessionEvents event; final CameraCaptureSession session; final CaptureRequest request; final CaptureResult result; CaptureSessionData(CaptureSessionEvents event, CameraCaptureSession session, CaptureRequest request, CaptureResult result) { this.event = event; this.session = session; this.request = request; this.result = result; } }
CameraCaptureSession.CaptureCallback
作成は別のメソッドです。
@NonNull private static CameraCaptureSession.CaptureCallback createCaptureCallback(final ObservableEmitter<CaptureSessionData> observableEmitter) { return new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) { } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { if (!observableEmitter.isDisposed()) { observableEmitter.onNext(new CaptureSessionData(CaptureSessionEvents.ON_COMPLETED, session, request, result)); } } @Override public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) { if (!observableEmitter.isDisposed()) { observableEmitter.onError(new CameraCaptureFailedException(failure)); } } @Override public void onCaptureSequenceCompleted(@NonNull CameraCaptureSession session, int sequenceId, long frameNumber) { } @Override public void onCaptureSequenceAborted(@NonNull CameraCaptureSession session, int sequenceId) { } }; }
これらすべてのメッセージのうち、 onCaptureCompleted
/ onCaptureFailed
関心があり、他のイベントは無視します。 プロジェクトで必要な場合は、簡単に追加できます。
これでObservable
を作成する準備ができました。
static Observable<CaptureSessionData> fromSetRepeatingRequest(@NonNull CameraCaptureSession captureSession, @NonNull CaptureRequest request) { return Observable .create(observableEmitter -> captureSession.setRepeatingRequest(request, createCaptureCallback(observableEmitter), null)); }
攻略
実際、このステップは前のステップと完全に類似しています。リクエストを繰り返すのではなく、1つだけ繰り返します。 これを行うには、 CameraCaptureSession.captureメソッドを使用します。
public abstract int capture(@NonNull CaptureRequest request, @Nullable CaptureCallback listener, @Nullable Handler handler) throws CameraAccessException;
上記で定義した関数を使用してCaptureCallback
を作成できるように、まったく同じパラメーターを受け入れます。
static Observable<CaptureSessionData> fromCapture(@NonNull CameraCaptureSession captureSession, @NonNull CaptureRequest request) { return Observable .create(observableEmitter -> captureSession.capture(request, createCaptureCallback(observableEmitter), null)); }
表面処理
Cameara2 APIを使用すると、デバイスからのデータを記録するために使用されるリクエストでSurfaceのリストを送信できます。 2つのSurfaceが必要です。
- 画面にプレビューを表示するには、
- JPEGファイルに画像を書き込む。
テクスチャビュー
画面にプレビューを表示するには、 TextureViewを使用します 。 TextureViewからSurfaceを取得するには、 TextureView.setSurfaceTextureListenerメソッドを使用することをお勧めします。
TextureView
は、 Surface
が使用可能になるとリスナーに通知します。
今度はPublishSubject
作成します。これは、 TextureView
がlistener
メソッドを呼び出すときにイベントを生成します。
private final PublishSubject<SurfaceTexture> mOnSurfaceTextureAvailable = PublishSubject.create(); @Override public void onCreate(@Nullable Bundle saveState){ mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener(){ @Override public void onSurfaceTextureAvailable(SurfaceTexture surface,int width,int height){ mOnSurfaceTextureAvailable.onNext(surface); } }); ... }
PublishSubject
を使用すると、複数のsubscribe
起こりうる問題を回避できsubscribe
。 onCreate
SurfaceTextureListener
一度設定すると、引き続き安心して使用できます。 PublishSubject
使用するPublishSubject
、 PublishSubject
サブスクライブでき、すべての署名者にイベントを配布できます。
Camera2 APIを使用する場合、画像サイズを明示的に設定できないことに関連する微妙な問題があります。カメラ自体は、 Surface
によって転送された寸法に基づいて、サポートする解像度のいずれかを選択します。 したがって、このようなトリックを行う必要があります。カメラでサポートされている画像サイズのリストを見つけ、最も魅力的なサイズを選択してから、バッファーサイズをまったく同じに設定します。
private void setupSurface(@NonNull SurfaceTexture surfaceTexture) { surfaceTexture.setDefaultBufferSize(mCameraParams.previewSize.getWidth(), mCameraParams.previewSize.getHeight()); mSurface = new Surface(surfaceTexture); }
この場合、比率を維持しながら画像を表示したい場合、必要な比率をTextureView
に設定する必要があります。 これを行うには、拡張してonMeasure
メソッドを再定義しonMeasure
。
public class AutoFitTextureView extends TextureView { private int mRatioWidth = 0; private int mRatioHeight = 0; ... public void setAspectRatio(int width, int height) { mRatioWidth = width; mRatioHeight = height; requestLayout(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (0 == mRatioWidth || 0 == mRatioHeight) { setMeasuredDimension(width, height); } else { if (width < height * mRatioWidth / mRatioHeight) { setMeasuredDimension(width, width * mRatioHeight / mRatioWidth); } else { setMeasuredDimension(height * mRatioWidth / mRatioHeight, height); } } } }
ファイルに書き込む
Surfaceから画像をファイルに保存するには、 ImageReaderクラスを使用します 。
ImageReader
サイズの選択に関するいくつかの言葉。 まず、サポートされているカメラから選択する必要があります。 次に、アスペクト比はプレビュー用に選択したものと一致する必要があります。
ImageReader
から画像の準備状況に関する通知を受け取ることができるように、 setOnImageAvailableListenerメソッドを使用します
void setOnImageAvailableListener (ImageReader.OnImageAvailableListener listener, Handler handler)
渡されたlistener
onImageAvailable
メソッドを1つだけ実装します。
Camera APIは、 ImageReader
提供するSurface
画像を書き込むたびに、このコールバックを呼び出します。
この操作をリアクティブにします: ImageReader
が画像を提供する準備ができるたびにメッセージを生成するObservable
を作成します。
@NonNull public static Observable<ImageReader> createOnImageAvailableObservable(@NonNull ImageReader imageReader) { return Observable.create(subscriber -> { ImageReader.OnImageAvailableListener listener = reader -> { if (!subscriber.isDisposed()) { subscriber.onNext(reader); } }; imageReader.setOnImageAvailableListener(listener, null); subscriber.setCancellable(() -> imageReader.setOnImageAvailableListener(null, null)); //remove listener on unsubscribe }); }
ここでは、 ObservableEmitter.setCancellable
からサブスクライブされていないときにlistener
を削除するメソッドObservableEmitter.setCancellable
を使用したことに注意してください。
ファイルへの書き込みは時間がかかる操作であるため、 fromCallable
メソッドを使用してリアクティブにします。
@NonNull public static Single<File> save(@NonNull Image image, @NonNull File file) { return Single.fromCallable(() -> { try (FileChannel output = new FileOutputStream(file).getChannel()) { output.write(image.getPlanes()[0].getBuffer()); return file; } finally { image.close(); } }); }
次の一連のアクションを設定できます: ImageReader
完成した画像が表示されたら、 Schedulers.io()
でファイルに書き込み、UIスレッドに切り替えて、ファイルの準備ができたことをUIに通知します。
private void initImageReader() { Size sizeForImageReader = CameraStrategy.getStillImageSize(mCameraParams.cameraCharacteristics, mCameraParams.previewSize); mImageReader = ImageReader.newInstance(sizeForImageReader.getWidth(), sizeForImageReader.getHeight(), ImageFormat.JPEG, 1); mCompositeDisposable.add( ImageSaverRxWrapper.createOnImageAvailableObservable(mImageReader) .observeOn(Schedulers.io()) .flatMap(imageReader -> ImageSaverRxWrapper.save(imageReader.acquireLatestImage(), mFile).toObservable()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(file -> mCallback.onPhotoTaken(file.getAbsolutePath(), getLensFacingPhotoType())) ); }
プレビューを実行
だから、徹底的に準備しました。 アプリケーションの動作に必要な基本的な非同期アクションのObservable
を作成できます。 最も興味深いのは、ジェットフローの構成です。
ウォームアップするには、 SurfaceTexture
を使用する準備SurfaceTexture
できたらカメラを開いてみましょう。
Observable<Pair<CameraRxWrapper.DeviceStateEvents, CameraDevice>> cameraDeviceObservable = mOnSurfaceTextureAvailable .firstElement() .doAfterSuccess(this::setupSurface) .doAfterSuccess(__ -> initImageReader()) .toObservable() .flatMap(__ -> CameraRxWrapper.openCamera(mCameraParams.cameraId, mCameraManager)) .share();
ここでのキー演算子はflatMap
です。
この場合、 SurfaceTexture
準備SurfaceTexture
を受信すると、 openCamera
関数を実行しSurfaceTexture
それによって作成されたObservable
からイベントをさらにジェットストリームにopenCamera
ます。
また、チェーンの最後でshare
演算子が使用される理由を理解することも重要です。 これは、一連のステートメントpublish().refCount()
と同等です。
このマーブルダイアグラムを長時間見ると、その結果はPublishSubject
を使用した結果と非常によく似ていることがPublishSubject
ます。 実際、同様の問題を解決していますObservable
数回サブスクライブする場合、毎回カメラを開く必要はありません。
便宜上、さらにいくつかのObservableを紹介しましょう。
Observable<CameraDevice> openCameraObservable = cameraDeviceObservable .filter(pair -> pair.first == CameraRxWrapper.DeviceStateEvents.ON_OPENED) .map(pair -> pair.second) .share(); Observable<CameraDevice> closeCameraObservable = cameraDeviceObservable .filter(pair -> pair.first == CameraRxWrapper.DeviceStateEvents.ON_CLOSED) .map(pair -> pair.second) .share();
openCameraObservable
は、カメラが正常に開かれたときにイベントを生成し、 openCameraObservable
は閉じられたときにイベントを生成します。
もう1つのステップを踏みましょう。カメラを正常に開いた後、セッションを開きます。
Observable<Pair<CameraRxWrapper.CaptureSessionStateEvents, CameraCaptureSession>> createCaptureSessionObservable = openCameraObservable .flatMap(cameraDevice -> CameraRxWrapper .createCaptureSession(cameraDevice, Arrays.asList(mSurface, mImageReader.getSurface())) ) .share();
同様に、セッションの正常な開始または終了を示す別のObservable
ペアを作成します。
Observable<CameraCaptureSession> captureSessionConfiguredObservable = createCaptureSessionObservable .filter(pair -> pair.first == CameraRxWrapper.CaptureSessionStateEvents.ON_CONFIGURED) .map(pair -> pair.second) .share(); Observable<CameraCaptureSession> captureSessionClosedObservable = createCaptureSessionObservable .filter(pair -> pair.first == CameraRxWrapper.CaptureSessionStateEvents.ON_CLOSED) .map(pair -> pair.second) .share();
最後に、プレビューを表示するための繰り返しクエリを指定できます。
Observable<CaptureSessionData> previewObservable = captureSessionConfiguredObservable .flatMap(cameraCaptureSession -> { CaptureRequest.Builder previewBuilder = createPreviewBuilder(cameraCaptureSession, mSurface); return CameraRxWrapper.fromSetRepeatingRequest(cameraCaptureSession, previewBuilder.build()); }) .share();
これでpreviewObservable.subscribe()
実行するだけです-カメラからのライブ画像が画面に表示されます!
小さな余談。 中間のObservable
すべて折りたたむと、次の一連の演算子が得られます。
mOnSurfaceTextureAvailable .firstElement() .doAfterSuccess(this::setupSurface) .toObservable() .flatMap(__ -> CameraRxWrapper.openCamera(mCameraParams.cameraId, mCameraManager)) .filter(pair -> pair.first == CameraRxWrapper.DeviceStateEvents.ON_OPENED) .map(pair -> pair.second) .flatMap(cameraDevice -> CameraRxWrapper .createCaptureSession(cameraDevice, Arrays.asList(mSurface, mImageReader.getSurface())) ) .filter(pair -> pair.first == CameraRxWrapper.CaptureSessionStateEvents.ON_CONFIGURED) .map(pair -> pair.second) .flatMap(cameraCaptureSession -> { CaptureRequest.Builder previewBuilder = createPreviewBuilder(cameraCaptureSession, mSurface); return CameraRxWrapper.fromSetRepeatingRequest(cameraCaptureSession, previewBuilder.build()); }) .subscribe();
プレビューを表示するにはこれで十分です。 印象的でしょ?
, , . , . Observable
.
, subscribe
Disposable
. CompositeDisposable
.
private final CompositeDisposable mCompositeDisposable = new CompositeDisposable(); private void unsubscribe() { mCompositeDisposable.clear(); }
mCompositeDisposable.add(...subscribe())
, , .
CaptureRequest
, , , createPreviewBuilder
, . , .
@NonNull CaptureRequest.Builder createPreviewBuilder(CameraCaptureSession captureSession, Surface previewSurface) throws CameraAccessException { CaptureRequest.Builder builder = captureSession.getDevice().createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); builder.addTarget(previewSurface); setup3Auto(builder); return builder; }
preview, Surface , Auto Focus, Auto Exposure Auto White Balance ( A). , .
private void setup3Auto(CaptureRequest.Builder builder) { // Enable auto-magical 3A run by camera device builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO); Float minFocusDist = mCameraParams.cameraCharacteristics.get(CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE); // If MINIMUM_FOCUS_DISTANCE is 0, lens is fixed-focus and we need to skip the AF run. boolean noAFRun = (minFocusDist == null || minFocusDist == 0); if (!noAFRun) { // If there is a "continuous picture" mode available, use it, otherwise default to AUTO. int[] afModes = mCameraParams.cameraCharacteristics.get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES); if (contains(afModes, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)) { builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); } else { builder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO); } } // If there is an auto-magical flash control mode available, use it, otherwise default to // the "on" mode, which is guaranteed to always be available. int[] aeModes = mCameraParams.cameraCharacteristics.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES); if (contains(aeModes, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)) { builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); } else { builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON); } // If there is an auto-magical white balance control mode available, use it. int[] awbModes = mCameraParams.cameraCharacteristics.get(CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES); if (contains(awbModes, CaptureRequest.CONTROL_AWB_MODE_AUTO)) { // Allow AWB to run auto-magically if this device supports this builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO); } }
, RxBinding , .
private final PublishSubject<Object> mOnShutterClick = PublishSubject.create(); public void takePhoto() { mOnShutterClick.onNext(this); }
. , preview ( , ). combineLatest.
Observable.combineLatest(previewObservable, mOnShutterClick, (captureSessionData, o) -> captureSessionData)
previewObservable, .
.firstElement().toObservable()
, .
.flatMap(this::waitForAf) .flatMap(this::waitForAe)
, , .
.flatMap(captureSessionData -> captureStillPicture(captureSessionData.session))
:
Observable.combineLatest(previewObservable, mOnShutterClick, (captureSessionData, o) -> captureSessionData) .firstElement().toObservable() .flatMap(this::waitForAf) .flatMap(this::waitForAe) .flatMap(captureSessionData -> captureStillPicture(captureSessionData.session)) .subscribe(__ -> { }, this::onError)
, captureStillPicture
.
@NonNull private Observable<CaptureSessionData> captureStillPicture(@NonNull CameraCaptureSession cameraCaptureSession) { return Observable .fromCallable(() -> createStillPictureBuilder(cameraCaptureSession.getDevice())) .flatMap(builder -> CameraRxWrapper.fromCapture(cameraCaptureSession, builder.build())); }
: , capture – . STILL_PICTURE
, Surface
, , , . , JPEG.
@NonNull private CaptureRequest.Builder createStillPictureBuilder(@NonNull CameraDevice cameraDevice) throws CameraAccessException { final CaptureRequest.Builder builder; builder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE); builder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CameraMetadata.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE); builder.addTarget(mImageReader.getSurface()); setup3Auto(builder); int rotation = mWindowManager.getDefaultDisplay().getRotation(); builder.set(CaptureRequest.JPEG_ORIENTATION, CameraOrientationHelper.getJpegOrientation(mCameraParams.cameraCharacteristics, rotation)); return builder; }
, , . onPause
.
Observable.combineLatest(previewObservable, mOnPauseSubject, (state, o) -> state) .firstElement().toObservable() .doOnNext(captureSessionData -> captureSessionData.session.close()) .flatMap(__ -> captureSessionClosedObservable) .doOnNext(cameraCaptureSession -> cameraCaptureSession.getDevice().close()) .flatMap(__ -> closeCameraObservable) .doOnNext(__ -> closeImageReader()) .subscribe(__ -> unsubscribe(), this::onError);
, API.
結論
, preview . . . .
RxJava API. , Callback Hell , . !