brunch

You can make anything
by writing

C.S.Lewis

by 서준수 Apr 02. 2019

안드로이드 카메라 예제 (1/2)

Camera2 API

안드로이드 카메라 예제 스터디 (1)


안드로이드 롤리팝 5.0 (API21)부터 기존의 Camera API가 Camera2 API로 변경되었다.


Camera2 API는 FCam API를 개발한 Marc Levoy에 의해서 만들어졌다.

FCam은 스탠퍼드 컴퓨터 그래픽 연구소의 Marc Levoy 그룹과 노키아 팔로 알토 연구소의 Kari Pulli 팀의 프로그램 동작이 가능한 카메라와 컴퓨터를 사용한 사진 촬영에 대한 Camera 2.0에 관한 공동 연구 프로젝트의 결과다. Marc Levoy은 Google에서 팀을 이끌기 위해서 스탠퍼드 대학을 은퇴했다. 그렇게 현재 안드로이드의 Camera2 API로 이어져 온 것이다.


최고의 예제는 구글의 공식 기본 카메라 예제다. 잘 짜여있지만 카메라 그 자체를 바로 이해하기엔 약간 복잡한 면도 있었다. 그도 그럴 것이 초기 버전에서 몇 번의 업데이트가 있었을 것이기 때문이다.


따라서 여기서는 비록 구글 예제에 비해서는 허술한 점이 많지만 그만큼 쉽고 직관적인 예제가 있던 한 블로그예제를 기본으로 한다.


1. TextureView.setSurfaceTextureListener 등록

카메라 앱이 resume시 mSurfaceTextureListener 등록한다. 카메라 프리뷰를 보여줄 때 사용할 뷰이다.


mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);

private TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() {

@Override
 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
     // TODO Auto-generated method stub
     Log.e(TAG, "onSurfaceTextureAvailable, width = " + width + ", height = " + height);
     openCamera();
 }

...

}



2. Camera ID 및 characteristics 가져오기

CameraManager의 getCameraIdList() 함수를 호출하여 스마트폰에 탑재된 카메라의 식별자(cameraId)를 얻을 수 있다. getCameraIdList() 함수의 리턴 값은 문자열 배열이다.


cameraId를 getCameraCharacteristics() 인자로 넘겨주면 CameraCharacteristics 객체를 가져올 수 있다.


private String getBackFacingCameraId(CameraManager cManager) {
    try {
        for (final String cameraId : cManager.getCameraIdList()) {
            CameraCharacteristics characteristics = cManager.getCameraCharacteristics(cameraId);
            int cOrientation = characteristics.get(CameraCharacteristics.LENS_FACING);
            if (cOrientation == CameraCharacteristics.LENS_FACING_BACK) return cameraId;
        }
    } catch (CameraAccessException e) {
        e.printStackTrace();
    }
    return null;
}



특이한 것은 해당 객체를 통해서 정보를 얻기 위해서는 필요한 정보의 getter 함수를 이용하지 않는다. 예를 들면 getLensFacing()이라고 사용하지 않는다. 대신에 다음과 같이 get함수에 필요한 정보에 대한 키를 인자로 넘겨주는 형식이다.


characteristics.get(CameraCharacteristics.LENS_FACING);


CameraCharacteristics.LENS_FACING는 카메라의 방향(전/후면)을 가져오기 위해 사용하는 key이고 다음과 같은 종류의 상수값을 얻는다.



따라서 getBackFacingCameraId() 함수는 후면 카메라의 cameraId를 가져오는 함수인 것이다. 그런데 잠깐 생각을 해보자. 만약 후면 카메라의 cameraId 외에 다른 카메라의 cameraId를 알 수 있다면 원하는 방향의 카메라로 전환이 가능한 것이 아닐까? 결론은 그렇다.


이를 확인하기 위해서 getCameraIdList()로 리턴된 문자열 배열의 모든 카메라 방향 정보를 로그로 출력해보자.


참고로 LG G8의 경우 크기 3의 문자열 배열이 리턴되었고 다음과 같이 2개의 후면 카메라와 1개의 전면 카메라에 대한 cameraId를 확인할 수 있다.

 


※ (의문점) G8의 경우 후면 카메라가 3개인데 왜 2개의 cameraId만 가져올까? 후면 망원 카메라의 cameraId를 가져오지 않은 것이다.

추가적으로 V40은 후면 일반에 대한 정보만 가져온다. (Orientation = 1 cameraId = 0) 그러나 실제로 cameraId 1로 카메라를 열면 전면 카메라가 열리고, cameraId 2로 열면 후면 광각 카메라가 실행된다.


다음과 같이 카메라의 종류를 선택하는 버튼을 추가해서 버튼 선택 시 cameraId를 설정해주면 원하는 카메라로 전환이 가능하다.


mNormalAngleButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        onPause();
        mCameraId = "0";
        openCamera();
    }
});

mWideAngleButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        onPause();
        mCameraId = "2";
        openCamera();
    }
});

mCameraDirectionButton.setOnClickListener(new View.OnClickListener() {

    @Override
    public void onClick(View v) {
        onPause();
        if (mCameraId.equals("1")) {
            mCameraId = "0";
        } else {
            mCameraId = "1";
        }
        openCamera();
    }
});



[참고] TextureView


SurfaceView를 사용하면 알파, 확대, 뒤집기 등이 제한적

안드로이드 Android API 14부터   TextureView를 지원 => SurfaceView의 단점을 보완



3. 카메라를 여는 함수


cameraManager.openCamera(mCameraId,   mStateCallback, null); // mCameraId를 적절히 변경해주면 원하는 카메라를 열 수 있다. (ex. 일반각, 광각, 전면, 후면)


private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {

 // 카메라 상태 콜백

...
 @Override
 public void onOpened(CameraDevice camera)

{

// 카메라 정상 오픈 시 호출됨

...

startPreview();  // 이때 프리뷰 보기 시작하면 된다.

}

...

}



프리뷰를 가져올 때

createCaptureRequest의 인자로 CameraDevice.TEMPLATE_PREVIEW를 넘겨준다.


protected void startPreview() {

...

SurfaceTexture texture mTextureView.getSurfaceTexture();
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
Surface surface = new Surface(texture);


 CaptureRequest.Builder mPreviewBuilder =  mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);

 mPreviewBuilder.addTarget(surface);

...

try {
    mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

        @Override
        public void onConfigured(CameraCaptureSession session) {
            // TODO Auto-generated method stub
            mPreviewSession = session;
            updatePreview();
        }

        @Override
        public void onConfigureFailed(CameraCaptureSession session) {
            // TODO Auto-generated method stub
            Toast.makeText(mContext, "onConfigureFailed", Toast.LENGTH_LONG).show();
        }
    }, null);

...

}



프리뷰 업데이트

캡처 세션 콜백을 받아서 onConfigured 호출 시에 프리뷰 업데이트를 한다.


mCameraDevice.createCaptureSession(Arrays.asList(surface), new CameraCaptureSession.StateCallback() {

...
 @Override

 public void onConfigured(CameraCaptureSession session) {

  ...

  updatePreview();

 }

...

}



4. 촬영할 사진 크기 설정

전/후면 카메라 정보를 가져온 것처럼 사진 크기에 대한 정보도 characteristics를 통해서 가져올 수 있다.

인자는 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP이고 리턴 타입은 StreamConfigurationMap이다.


StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);



해당 객체에는 카메라 관련 각종 지원 정보가 포함되어 있다. 이 정보 중에 카메라가 지원하는 사진 크기 목록도 있다. 다음과 같이 매개변수에 이미지 포맷을 지정하면 Size 배열이 리턴된다.


Size[] jpegSizes = map.getOutputSizes(ImageFormat.JPEG);



[참고] LG G8 지원 사진 크기 29개 (V40는 27개이며 리스트는 생략함)


   getHeight = 3024, getWidth = 4032
   getHeight = 2272, getWidth = 4032
   getHeight = 2160, getWidth = 3840
   getHeight = 2460, getWidth = 3280
   getHeight = 1848, getWidth = 3280
   getHeight = 1560, getWidth = 3280
   getHeight = 1080, getWidth = 2560
   getHeight = 1080, getWidth = 2268
   getHeight = 1536, getWidth = 2048
   getHeight = 1440, getWidth = 1920
   getHeight = 1080, getWidth = 1920
   getHeight = 1080, getWidth = 1440
   getHeight = 960, getWidth = 1440
   getHeight = 720, getWidth = 1440
   getHeight = 1152, getWidth = 1408
   getHeight = 960, getWidth = 1280
   getHeight = 768, getWidth = 1280
   getHeight = 720, getWidth = 1280
   getHeight = 1080, getWidth = 1080
   getHeight = 720, getWidth = 960
   getHeight = 540, getWidth = 960
   getHeight = 720, getWidth = 720
   getHeight = 540, getWidth = 720
   getHeight = 480, getWidth = 720
   getHeight = 480, getWidth = 640
   getHeight = 640, getWidth = 480
   getHeight = 288, getWidth = 352
   getHeight = 240, getWidth = 320
   getHeight = 144, getWidth = 176



카메라 전환을 적용한 버튼을 테스트하니 다음과 같이 후면 일반각, 후면 광각, 전/후면 전환이 정상적으로 되었다.


후면 일반, 광각, 전면 카메라 전환 테스트 (V40)


다음 편에서는 사진을 촬영하는 버튼에 대한 동작을 구현해 볼 것이다.




ref.)

FCam : http://fcam.garage.maemo.org/

카메라 예제 : https://myandroidarchive.tistory.com/1

깡샘 블로그 : https://kkangsnote.tistory.com/48

돼지왕의 놀이터 : https://aroundck.tistory.com/5912

구글 기본 카메라 예제 : https://github.com/googlesamples/android-Camera2Basic

TextureView 참고 :  https://aroundck.tistory.com/2075

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari