
ãåç¥ã®ããã«ã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; }
ããã§ãç®çã®æ¹åã®ã«ã¡ã©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));
ããã§ã¯ã 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) {
, 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 . . . .
[ Update : ]
RxJava API. , Callback Hell , . !