반응형

https://wp94.tistory.com/33

이전 포스트에서 SurfaceView에 대해 알아보았습니다. Surface가 생성될 때 카메라 프리뷰를 설정하는 메소드를 실행해주었는데 본 포스트에서 그 메소드에 대해 알아보겠습니다. 

 

[안드로이드] 밑바닥부터 딥러닝 호환 카메라 앱 만들기 #1-1 SurfaceView

https://wp94.tistory.com/32 #1에서 MainActivity.java의 전체적인 소스코드를 봤습니다. 본 포스트에서부터 변수 선언 부분을 낱낱이 파헤쳐봅시다. [안드로이드] 밑바닥부터 딥러닝 호환 카메라앱 만들기 #

wp94.tistory.com

    private Handler mHandler;
    private ImageReader mImageReader;
    @TargetApi(19)
    public void initCameraAndPreview() {
        HandlerThread handlerThread = new HandlerThread("CAMERA2");
        handlerThread.start();
        mHandler = new Handler(handlerThread.getLooper());
        Handler mainHandler = new Handler(getMainLooper());
        try {
            String mCameraId = "" + CameraCharacteristics.LENS_FACING_FRONT; // 후면 카메라 사용

            CameraManager mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(mCameraId);
            StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);

            Size largestPreviewSize = map.getOutputSizes(ImageFormat.JPEG)[0];
            Log.i("LargestSize", largestPreviewSize.getWidth() + " " + largestPreviewSize.getHeight());

            setAspectRatioTextureView(largestPreviewSize.getHeight(),largestPreviewSize.getWidth());

            mImageReader = ImageReader.newInstance(largestPreviewSize.getWidth(), largestPreviewSize.getHeight(), ImageFormat.JPEG,/*maxImages*/7);
            mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mainHandler);
            if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            mCameraManager.openCamera(mCameraId, deviceStateCallback, mHandler);
        } catch (CameraAccessException e) {
            Toast.makeText(this, "카메라를 열지 못했습니다.", Toast.LENGTH_SHORT).show();
        }
    }

camera2 API를 먼저 알아봅시다. camera2 API는 기존 camera API의 단순 기능들을 보완한 상위버전의 API라고 보시면 되겠습니다. 즉 기능이 더 많아졌다 정도로 생각하면 되겠습니다. 

HandlerThread는 스레드에 루퍼와 핸들러를 연결한 클래스입니다. 루퍼는 자신이 속한 스레드의 메시지큐에 추가되는 메시지를 기다리다가 꺼내서 이를 처리할 핸들러에 디스패치하는 기능을 합니다. 

위 소스코드를 읽어보자면 CAMERA2 HandlerThread 클래스를 선언하고 시작합니다. 돌아가고 있는 스레드의 루퍼(getLooper())를 이용해서 디스패치할 핸들러(mHandler)를 정의해줍니다. 스레드의 핸들러는 mHandler이고 메인 핸들러는 mainHandler로 정의해줍니다.이후 디바이스 기반 코드이기 때문에 권한 및 하드웨어 에러를 고려하여 try catch문으로 접근합니다. mCameraId는 전면 또는 후면 카메라 사용 키 값입니다. (전면 카메라가 셀카죠)

  • LENS_FACING_FRONT: 후면 카메라. value : 0
  • LENS_FACING_BACK: 전면 카메라. value : 1
  • LENS_FACING_EXTERNAL: 기타 카메라. value : 2

CameraManager 모든 카메라 관련 작업은 카메라 매니저와 함께합니다. 카메라 매니저를 통해 CameraCharacteristic을 가져옵니다 이건 말 그대로 카메라 특성을 담고있는 클래스라 보시면 됩니다. 위 소스코드에서 SCALER_STREAM_CONFIGURATION_MAP를 가져왔는데 이 안에 많은 정보들이 있으나 사용하는 정보는 getOutputSizes입니다.

getOutputSizes ( ) 함수에 이미지 포맷을 매개변수로 지정하면 지원하는 크기 목록이 Size 객체의 배열로 반환되는데, 이 값을 이용하여 사진 촬영 시 사진 크기를 지정할 수 있습니다.

출처: https://kkangsnote.tistory.com/48 [깡샘의 토마토]

setAspectRatioTextureView

는 현재 뷰의 종횡비를 설정해주는 것으로 위 코드에서 카메라의 가로 세로 사이즈를 입력했습니다.카메라 디바이스 세팅과 종횡비 설정을 한 것 같네요 그럼 카메라에서 입력받은 이미지들을 담을 객체를 만들어야겠습니다.ImageReader 객체를 정의해주고 파라미터로 가로, 세로, 파일 포맷, 버퍼 사이즈를 입력해줍니다. 그리고 리스너를 설정해주는데 위 코드에서 리스너를 커스텀하게 구현했습니다. 아래 리스너의 기능은 카메라에서 받은 이미지(바이트)를 버퍼에 넣고 바이트를 배열로 디코딩해준 후 비트맵으로 바꿔주는 기능입니다. 여기서 메인 핸들러에 할당한 이유는 카메라 스레드는 이미 카메라 데이터를 받아오는데 할당이 되어있는데 바이트2비트맵 연산까지 처리할 수 없기 때문에 메인 스레드에 할당하는겁니다.

private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
        @Override
        public void onImageAvailable(ImageReader reader) {

            Image image = reader.acquireNextImage();
            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            new SaveImageTask().execute(bitmap);
        }
    };

그 다음 카메라 허용에 대한 퍼미션 부분을 체크해주고

카메라 매니저의 오픈 카메라 메소드를 통해 카메라 세팅을 끝내줍니다. 여기서 파라미터로 deviceStateCallback객체가 존재하는데 다음과 같이 구현되어 있습니다.

private CameraDevice mCameraDevice;
private CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(CameraDevice camera) {
            mCameraDevice = camera;
            try {
                takePreview();
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            if (mCameraDevice != null) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
        }

        @Override
        public void onError(CameraDevice camera, int error) {
            Toast.makeText(MainActivity.this, "카메라를 열지 못했습니다.", Toast.LENGTH_SHORT).show();
        }
    };

객체는 카메라가 열렸을 때(onOpened) 닫혔을 때(onDisconnected) 에러났을 때(onError)로 구성되어 있습니다. 볼만한건 열렸을 때 호출되는 takePreview()겠군요 아래 코드가 있습니다.

private CaptureRequest.Builder mPreviewBuilder;
    public void takePreview() throws CameraAccessException {
        mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        mPreviewBuilder.addTarget(mSurfaceViewHolder.getSurface());
        mCameraDevice.createCaptureSession(Arrays.asList(mSurfaceViewHolder.getSurface(), mImageReader.getSurface()), mSessionPreviewStateCallback, mHandler);
    }

코드를 읽어보자면 카메라 디바이스에서 촬영요청을 담당할 객체(mPreviewBuilder)를 생성하고 그 객체가 타겟팅할 객체(mSurfaceViewHolder.getSurface())를 설정해줍니다. 그리고 카메라 디바이스에 대한 캡쳐세션을 생성해주는데 mSessionPreviewStateCallback이 들어가있고 아래와 같이 구현되어 있습니다.

private CameraCaptureSession mSession;
private CameraCaptureSession.StateCallback mSessionPreviewStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mSession = session;

            try {

                mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
                mSession.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            Toast.makeText(MainActivity.this, "카메라 구성 실패", Toast.LENGTH_SHORT).show();
        }
    };

객체는 세션 설정과 실패로 구성되어 있습니다. 세션 설정 안에는 모드와 지속적으로 촬영하는지, 자동 플래시 기능 이런 설정들이 담겨있네요 물론 하드웨어와 연동되기 때문에 트라이 캐치문을 사용했습니다. 그리고 위 설정을 반복 요청을 하도록 세션에 세팅해줍니다.

반응형

+ Recent posts