Giới thiệu camera 2 API trong Android (phần 1)

1. Project preparation

Retrolambda

buildscript {
 	dependencies {
  	classpath 'me.tatarka:gradle-retrolambda:3.6.0'
   }
}

apply plugin: 'me.tatarka.retrolambda'

Sau đó, chúng ta nâng version lên 8:

android {
  compileOptions {
	sourceCompatibility JavaVersion.VERSION_1_8
	targetCompatibility JavaVersion.VERSION_1_8
  }
}

RxJava2

compile "io.reactivex.rxjava2:rxjava:2.1.0"

RxAndroid

compile 'io.reactivex.rxjava2:rxandroid:2.0.1'

2. Camera2 API****

Ấn tượng đầu tiên

Google đã làm việc tích cực trên những sai lầm liên quan đến organising thread. Tất cả các hoạt động được thực hiện không đồng bộ, với các thông báo qua callbacks. Cụ thể, bạn có thể chọn các thread trong đó các phương pháp gọi lại gửi đến Handler tương ứng sẽ được gọi. Như thường lệ, làm việc với các callback không đồng bộ tiếp theo được sử dụng trong API này có thể nhanh chóng rơi xuống Callback Hell.

Các bước để chụp một bức ảnh

  • Chọn device
  • mở device
  • mở session
  • chạy preview
  • chụp một bức ảnh bằng cách ấn button
  • đóng session
  • đóng device

Chọn device

Đầu tiên chúng ta sẽ cần đến CameraManager

mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);

Class này cho phép chúng ta nhận được thông tin về camera trong hệ thống. Có thể có nhiều camera, với điện thoại thông minh thường có hai phía trước và phía sau.

Hãy lấy một danh sách các cameras:

String[] cameraIdList = manager.getCameraIdList();

Vì vậy, nó chỉ là một danh sách các chuỗi ID. Bây giờ hãy lấy một danh sách các đặc tính cho mỗi máy ảnh:

for (String cameraId : cameraIdList) {
  CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);
...
}

CameraCharacteristics chứa một số lượng key rất lớn có thể sử dụng để chứa thông tin về camera.

Phổ biến nhất ở giai đoạn này của sự lựa chọn máy ảnh, chúng nhìn vào nơi mà các máy ảnh được hướng dẫn. Đối với điều này chúng ta cần phải có được giá trị cho các key CameraCharacteristics.LENS_FACING:

Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);

Máy ảnh có thể ở phía trước (CameraCharacteristics.LENS_FACING_FRONT), phía sau (CameraCharacteristics.LENS_FACING_BACK) hoặc bên ngoài (CameraCharacteristics.LENS_FACING_EXTERNAL).

Chức năng lựa chọn máy ảnh với orientation reference có thể trông giống như sau:

@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];
}

Bây giờ chúng tôi có một ID cho máy ảnh với required orientation (nếu chúng ta cần là không tìm thấy). Mọi thứ vẫn còn khá đơn giản, và không có hành động không đồng bộ.

Tạo một observable

Bây giờ chúng ta đi đến các mothed API không đồng bộ. Mỗi phương thức trong đó có thể được biến thành một Observable bằng cách sử dụng method create.

OpenCamera Trước khi sử dụng, chúng ta cần mở thiết bị này với sự trợ giúp của method CameraManager.openCamera.

Void openCamera (String cameraId, CameraDevice.StateCallback callback, Handler handler)

Trong method này, chúng ta chuyển ID của máy ảnh đã chọn sang callback để nó có thể nhận được kết quả không đồng bộ; Và Handler nếu chúng ta muốn gọi method callback trong thread của Handler này.

Đây là nơi chúng ta đi qua method không đồng bộ đầu tiên. Nó có thể hiểu được, vì sự khởi động của thiết bị là một quá trình dài và tốn kém.

Chúng ta hãy nhìn vào CameraDevice.StateCallback.

Trong thế giới reactive, các method này sẽ tương ứng với các sự kiện. Chúng ta hãy tạo ra một Observable sẽ phát ra một sự kiện khi API của camera gọi callbacks onOpened, onClosed và onDisconnected. Để phân biệt các sự kiện này, chúng ta tạo ra enum:

public enum DeviceStateEvents {
  ON_OPENED,
  ON_CLOSED,
  ON_DISCONNECTED
}

Đối với reactive stream (từ đây, tôi sẽ gọi reactive stream là trình tự của các reactive operator) để có thể làm bất cứ điều gì với thiết bị, chúng ta sẽ thêm một liên kết tới CameraDevice trong sự kiện đã được phát. Phương pháp đơn giản nhất là phát ra cặp <DeviceStateEvents, CameraDevice>. Để tạo một Observable, chúng ta sẽ sử dụng method create.

Dưới đây là signature của phương thức tạo:

public static <T> Observable<T> create(ObservableOnSubscribe<T> source)

Điều này có nghĩa là chúng ta cần phải pass đối tượng interface ObservableOnSubscribe. Interface này chỉ chứa một method:

void subscribe(@NonNull ObservableEmitter<T> e) throws Exception;

Được gọi là mỗi lần Observer đăng ký vào Observable của chúng ta. Hãy nhìn vào ObservableEmitter:

public interface ObservableEmitter<T> extends Emitter<T> {
  void setDisposable(@Nullable Disposable d);
  void setCancellable(@Nullable Cancellable c);
  boolean isDisposed();
  ObservableEmitter<T> serialize();
}

Nó đã được tìm kiếm hữu ích. Sử dụng phương pháp setDisposable / setCancellable bạn có thể thiết lập một action sẽ được thực hiện khi Observable signs của chúng tôi tắt. Điều này rất hữu ích nếu chúng ta mở một resource, trong quá trình tạo ra một Observable, chúng ta cần đóng lại. Chúng ta có thể đã tạo một Disposable để đóng thiết bị khi hủy đăng ký, nhưng chúng ta muốn phản ứng lại sự kiện onClosed, vì vậy chúng ta sẽ không làm thế. Phương pháp isDisposed cho phép chúng ta kiểm tra xem có bất kỳ điều gì khác được đăng ký với Observable hay không.

Lưu ý rằng interface có tên ObservableEmitter extends interface Emitter:

public interface Emitter<T> {
  void onNext(@NonNull T value);
  void onError(@NonNull Throwable error);
  void onComplete();
}

Đây là những method chúng ta cần! Chúng ta sẽ gọi onNext mỗi lần Camera API callback callback CameraDevice.StateCallback onOpened / onClosed / onDisconnected; Và chúng ta sẽ gọi onError khi Camera API gọi hàm callback onError.

Vì vậy, hãy áp dụng kiến thức của chúng ta. Method tạo ra một Observable có thể giống như thế này:

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);
    });
}

Như tôi đã nói, tất cả các method cho Camera2 API chấp nhận Handler là một trong các tham số. Trong việc chuyển null, chúng ta sẽ nhận được callbacks trong thread hiện tại. Trong trường hợp của chúng ta, đây là chủ đề trong đó đăng ký đã được gọi, là Main Thread.

CreateCaptureSession

Bây giờ chúng ta có CameraDevice, chúng ta có thể mở CaptureSession. Đối với điều này, chúng tôi sẽ sử dụng method CameraDevice.createCaptureSession. Đây là signature:

void createCaptureSession(
  @NonNull List<Surface> outputs,
  @NonNull CameraCaptureSession.StateCallback callback,
  @Nullable Handler handler) throws CameraAccessException;

Khi nhập cảnh, danh sách Surface được đưa ra (chúng ta sẽ xem xét nơi để lấy thông tin này từ sau) cũng như CameraCaptureSession.StateCallback. Hãy kiểm tra các method nó chứa:

Chúng ta đã biết làm thế nào để chinh phục callbacks! Chúng ta sẽ tạo một Observable, sẽ phát ra một sự kiện khi Camera API gọi các method này. Để phân biệt chúng, chúng ta sẽ tạo ra enum:

public enum CaptureSessionStateEvents {
  ON_CONFIGURED,
  ON_READY,
  ON_ACTIVE,
  ON_CLOSED,
  ON_SURFACE_PREPARED
}

Vì vậy, CameraCaptureSession nằm trong reactive stream, chúng ta sẽ tạo ra không chỉ CaptureSessionStateEvent, mà còn là cặp <CaptureSessionStateEvents, CameraCaptureSession>. Vì vậy, đây là phương pháp tạo ra một Quan sát có thể trông giống như (một lần nữa, các xác minh được xóa để làm cho nó dễ đọc hơn):

@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

Đối với hình ảnh trực tiếp từ camera xuất hiện trên màn hình, chúng ta cần liên tục nhận hình ảnh mới từ thiết bị và gửi chúng để hiển thị. Có một phương pháp thuận tiện trong API cho nó.

CameraCaptureSession.setRepeatingRequest:

int setRepeatingRequest(@NonNull CaptureRequest request,
            @Nullable CaptureCallback listener, @Nullable Handler handler)
            throws CameraAccessException;

Chúng tôi sẽ áp dụng một cách tiếp cận đã được biết đến để làm cho operation reactive. Chúng ta hãy cùng xem xét:

Một lần nữa, chúng ta muốn phân biệt các sự kiện được tạo ra, vì vậy hãy tạo ra enum

public enum CaptureSessionEvents {
 ON_STARTED,
 ON_PROGRESSED,
 ON_COMPLETED,
 ON_SEQUENCE_COMPLETED,
 ON_SEQUENCE_ABORTED
}

Ngoài ra, chúng ta thấy rằng một số lượng lớn thông tin được gửi đến các method, chúng ta muốn bao gồm trong reactive stream, bao gồm CameraCaptureSession, CaptureRequest và CaptureResult. Như đơn giản chỉ bằng cách sử dụng Pải sẽ không phù hợp, chúng ta sẽ tạo ra một POJO:

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;
  }
}

Chúng tôi sẽ chuyển việc tạo ra CameraCaptureSession.CaptureCallback thành một method riêng biệt:

@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) {
      }
  };
}

Từ tất cả các message này, chúng ta quan tâm đến onCaptureCompleted / onCaptureFailed, và chúng ta sẽ bỏ qua phần còn lại. Nếu bạn cần chúng cho dự án của bạn, không khó để thêm chúng.

Bây giờ mọi thứ đã sẵn sàng, vì vậy chúng ta có thể tạo ra một Observable:

static Observable<CaptureSessionData> fromSetRepeatingRequest(@NonNull CameraCaptureSession captureSession, @NonNull CaptureRequest request) {
        return Observable
            .create(observableEmitter -> captureSession.setRepeatingRequest(request, createCaptureCallback(observableEmitter), null));
}

Nguon: https://techblog.badoo.com/blog/2017/06/07/reactive-selfies-with-camera2-api-on-android-part-1/