Slide 1

Slide 1 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API MARCOS AMBROSI

Slide 2

Slide 2 text

Android Developer @ CodigoDelSur @MarcosAmbrosi mambrosi mmark MARCOS AMBROSI

Slide 3

Slide 3 text

CUSTOM VS BUILT IN

Slide 4

Slide 4 text

Custom Built in

Slide 5

Slide 5 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API BUILT IN CAMERA - LO BUENO ▸ Fácil de implementar (MediaStore.ACTION_IMAGE_CAPTURE) ▸ Delegamos el trabajo a una app de terceros.

Slide 6

Slide 6 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API BUILT IN CAMERA - LO NO TAN BUENO ▸ Delegamos el trabajo a una app de terceros ▸ No hay control sobre la UI ▸ No hay manejo de errores

Slide 7

Slide 7 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CUSTOM CAMERA - LO BUENO ▸ Mejor integración con la UI de nuestra app ▸ Podemos manejar errores, modos, etc ▸ Custom features (grabar solo 10 segundos)

Slide 8

Slide 8 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CUSTOM CAMERA - LO NO TAN BUENO ▸ Implementación compleja ▸ Lidiar con fragmentación ▸ Errores pueden costar caro

Slide 9

Slide 9 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA1

Slide 10

Slide 10 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA1 ▸ Basic Modes: Preview, Capture & Record ▸ Auto Focus, Flash… ▸ No RAW support ▸ Difícil de implementar custom features (HDR, Burst, etc) ▸ No hay controles manuales (aperture, shutter speed)

Slide 11

Slide 11 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA1 - ARQUITECTURA

Slide 12

Slide 12 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA2

Slide 13

Slide 13 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA2 - ARQUITECTURA

Slide 14

Slide 14 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA2 ▸ A partir de API Level 21 ▸ DSLR ▸ Total re-work (Camera1 es deprecada en API 21) ▸ On-device post processing (HDR, Focus Stacking) ▸ RAW support ▸ Manual exposure and shutter speed ▸ Mejor manejo de Multi-core CPU & GPU ▸ HAL update

Slide 15

Slide 15 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA2 - CLASES ▸ CameraDevice: cámara conectada al dispositivo ▸ CameraManager: servicio para obtener un CameraDevice ▸ CameraCaptureSession: ▸ configura los outputs para CameraDevice ▸ se encarga de el manejo de requests ▸ CaptureResult: resultado de una ImageCapture request

Slide 16

Slide 16 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CAMERA2 APPLICATION CAMERA DEVICE CAPTURE REQUEST CAPTURE RESULT

Slide 17

Slide 17 text

THE “DOUBLE R” ISSUE

Slide 18

Slide 18 text

ROTATION RESOLUTION &

Slide 19

Slide 19 text

RESOLUTION

Slide 20

Slide 20 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API THE ISSUE SURFACEVIEW Camera Device SurfaceView.getSurfaceTexture().setDefaultBufferSize(width, height);

Slide 21

Slide 21 text

768px 1184px Nexus 4 Ratio: 1.54 CAMERA1 Camera.getSupportedPreviewSizes() 1280 X 96O 1.33 1280 X 720 1.77 800 X 480 1.66 720 X 480 1.5 640 X 480 1.33 576 X 432 1.33 480 X 320 1.5 384 X 288 1.33 352 X 288 1.22 320 X 240 1.33 CAMERA2 StreamConfigurationMap.getOutputSizes(SurfaceTexture.class)

Slide 22

Slide 22 text

ROTATION

Slide 23

Slide 23 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API THE ISSUE CaptureRequest.Builder .set(CaptureRequest.JPEG_ORIENTATION, orientation)

Slide 24

Slide 24 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API NEXUS 4 0° 180° 270° 90°

Slide 25

Slide 25 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API SAMSUNG GALAXY S3 0° 180° 270° 90°

Slide 26

Slide 26 text

STEP BY STEP GUIDE CAMERA2

Slide 27

Slide 27 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API 
 
 
 
 
 1) DEFINIR MI SURFACEVIEW

Slide 28

Slide 28 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private TextureView.SurfaceTextureListener mSurfaceTextureListener =
 new TextureView.SurfaceTextureListener() {
 @Override
 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
 //SETEO OUTPUTS
 //INICIO LA CAMARA
 }
 
 @Override
 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
 
 }
 
 @Override
 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
 return false;
 }
 
 @Override
 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
 }
 }; 2) DECLARO UN LISTENER PARA EL SURFACEVIEW

Slide 29

Slide 29 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API CameraManager cameraManager = (CameraManager)getSystemService(Context.CAMERA_SERVICE); 
 
 
 for (String cameraId : cameraManager.getCameraIdList()) { 
 CameraCharacteristics cameraCharacteristics =
 cameraManager.getCameraCharacteristics(cameraId);
 
 //We are interested in the back camera only 
 if (CameraCharacteristics.LENS_FACING_FRONT == cameraCharacteristics.
 get(CameraCharacteristics.LENS_FACING)) {
 continue;
 }
 
 
 StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.
 get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); } 3) CONFIGURAMOS LOS OUTPUTS 1/2

Slide 30

Slide 30 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API StreamConfigurationMap streamConfigurationMap = cameraCharacteristics. get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); mPreviewSize = getPreferredPreviewSize(streamConfigurationMap (SurfaceTexture.class), width, height); 4) CONFIGURAMOS LOS OUTPUTS 2/2

Slide 31

Slide 31 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private void openCamera() { 
 CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); 
 try { 
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 if (checkSelfPermission(Manifest.permission.CAMERA)
 != PackageManager.PERMISSION_GRANTED) {
 requestCameraPermission();
 return;
 }
 } cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mBackgroundHandler); 
 } catch (CameraAccessException e) {
 e.printStackTrace();
 }
 } 5) INICIAMOS LA CAMARA

Slide 32

Slide 32 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {
 @Override
 public void onOpened(CameraDevice camera) {
 mCameraDevice = camera;
 createCameraPreviewSession();
 }
 
 @Override
 public void onDisconnected(CameraDevice camera) {
 camera.close();
 mCameraDevice = null;
 }
 
 @Override
 public void onError(CameraDevice camera, int error) {
 camera.close();
 mCameraDevice = null;
 }
 }; 6) DEFINIMOS UN LISTENER PARA EL INICIO DE LA CAMARA

Slide 33

Slide 33 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private void createCameraPreviewSession() { 
 SurfaceTexture texture = mTextureView.getSurfaceTexture(); 
 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
 Surface previewSurface = new Surface(texture); . . . } 7) CREAMOS LA SESION DE PREVIEW 1/2

Slide 34

Slide 34 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API try {
 // We set up a CaptureRequest.Builder with the output Surface.
 mPreviewRequestBuilder
 = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
 mPreviewRequestBuilder.addTarget(previewSurface);
 
 mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() {
 @Override
 public void onConfigured(CameraCaptureSession session) {
 // The camera is already closed
 if (null == mCameraDevice) {
 return;
 }
 
 // When the session is ready, we start displaying the preview.
 mCaptureSession = session;
 try {
 // Auto focus should be continuous for camera preview.
 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
 CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
 // Flash is automatically enabled when necessary.
 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
 
 
 mPreviewRequest = mPreviewRequestBuilder.build(); //We tell the camera to start the preview
 mCaptureSession.setRepeatingRequest(mPreviewRequest,
 mSessionCaptureCallback,
 null);
 
 } catch (CameraAccessException e) {
 e.printStackTrace();
 }
 }
 
 @Override
 public void onConfigureFailed(CameraCaptureSession session) {
 
 }
 }, null);
 8) CREAMOS LA SESION DE PREVIEW 2/2

Slide 35

Slide 35 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API 
 @Override
 public void onResume() {
 super.onResume();
 startBackgroundThread();
 
 // 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(mTextureView.getWidth(), mTextureView.getHeight());
 } else {
 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
 }
 }
 
 @Override
 public void onPause() {
 closeCamera();
 super.onPause();
 } 9) MANEJAMOS LOS RECURSOS DE LA CAMARA EN EL CICLO DE VIDA DEL FRAGMENT/ACTIVITY

Slide 36

Slide 36 text

STEP BY STEP TAKE PICTURE

Slide 37

Slide 37 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API // For still image captures, we use the largest available size.
 Size largest = Collections.max(
 Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
 new CompareSizesByArea()); mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
 ImageFormat.JPEG, /*maxImages*/2); 
 mImageReader.setOnImageAvailableListener(
 mOnImageAvailableListener, mBackgroundHandler); 1) CREAMOS UN IMAGE READER

Slide 38

Slide 38 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
 = new ImageReader.OnImageAvailableListener() {
 
 @Override
 public void onImageAvailable(ImageReader reader) {
 mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mFile));
 }
 
 }; 2) CREAMOS UN LISTENER PARA EL IMAGE READER

Slide 39

Slide 39 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private void lockFocus() {
 try { // Tell #mCaptureCallback to wait for the lock.
 mState = STATE_WAITING_LOCK;
 // This is how to tell the camera to lock focus.
 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
 CameraMetadata.CONTROL_AF_TRIGGER_START); //Notify capture session that the builder has change mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
 mBackgroundHandler);
 } catch (CameraAccessException e) {
 e.printStackTrace();
 }
 } 3) BLOQUEAMOS EL FOCO

Slide 40

Slide 40 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API private CameraCaptureSession.CaptureCallback mSessionCaptureCallback = new CameraCaptureSession.CaptureCallback() {
 
 
 private void process(CaptureResult result) {
 switch(mState) {
 case STATE_PREVIEW:
 // Do nothing
 break;
 case STATE_WAIT_LOCK:
 Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
 if(afState == CaptureRequest.CONTROL_AF_STATE_FOCUSED_LOCKED) {
 captureStillImage();
 }
 break;
 }
 }
 
 @Override
 public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) {
 super.onCaptureStarted(session, request, timestamp, frameNumber);
 }
 
 @Override
 public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
 super.onCaptureProgressed(session, request, partialResult);
 }
 
 @Override
 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
 super.onCaptureCompleted(session, request, result);
 process(result);
 }
 
 @Override
 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
 super.onCaptureFailed(session, request, failure);
 }
 
 };
 4) MODIFICAMOS EL CALLBACK DE CAPTURA Y PROCESAMOS EL RESULTADO

Slide 41

Slide 41 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API 
 private void captureStillImage() {
 try { 
 CaptureRequest.Builder captureStillBuilder = mCameraDevice. createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); 
 captureStillBuilder.addTarget(mImageReader.getSurface());
 
 int rotation = getWindowManager().getDefaultDisplay().getRotation();
 captureStillBuilder.set(CaptureRequest.JPEG_ORIENTATION,
 ORIENTATIONS.get(rotation));
 
 CameraCaptureSession.CaptureCallback captureCallback =
 new CameraCaptureSession.CaptureCallback() {
 @Override
 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { 
 super.onCaptureCompleted(session, request, result);
 
 unLockFocus();
 }
 };
 
 mCaptureSession.capture(
 captureStillBuilder.build(), captureCallback, null
 );
 
 } catch (CameraAccessException e) {
 e.printStackTrace();
 }
 } 5) PEDIMOS AL CAPTURE SESSION QUE HAGA LA CAPTURA

Slide 42

Slide 42 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API 
 private void unLockFocus() {
 try {
 
 mState = STATE_PREVIEW;
 
 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
 CaptureRequest.CONTROL_AF_TRIGGER_CANCEL);
 
 mCaptureSession.capture(mPreviewRequestBuilder.build(),
 mSessionCaptureCallback, mBackgroundHandler);
 
 } catch (CameraAccessException e) {
 e.printStackTrace();
 }
 } 7) DESBLOQUEAMOS EL FOCO

Slide 43

Slide 43 text

ANDROID CUSTOM CAMERA WITH CAMERA2 API SOURCES ▸ DevBytes: Android L Developer Preview - Camera2 API ▸ Android Camera2 API by Huyen Tue Dao Android Camera2 API by Huyen Tue Dao ▸ Camera 2 API by Mobile Application Tutorials ▸ https://github.com/googlesamples/android- Camera2Basic ▸ developer.android.com

Slide 44

Slide 44 text

GRACIAS!