Android

Camera2 API 개념부터 구현까지

show2888 2023. 4. 6. 23:04
반응형

- 개념

하나의 안드로이드 디바이스는 여러개의 카메라를 갖고있다. 각각의 카메라는 CameraDevice 정의된다. CameraDevice Raw데이터에서 Preview, Photo, Record 스트림을 동시에 출력하며 Device마다 각기 다른 pipeline을 갖고있다. 이러한 pipeline은 CameraCaptureSession이 갖고있으며 CaptureRequest를 통해 선택된 pipeline으로 CameraDevice 부터 나온 frame 전달받는다. CaptureSession 살아있는동안 수많은 CaptureRequest 보낼 있다. 각각의 CaptureReuqest active configuration 바꿀 있고, 파이프라인도 선택 있다.

구성요소)

  • CamaraManager
    - 시스템 서비스
    - 사용 가능한 카메라, 카메라 기능들을 쿼리할 수 있고 카메라를 열 수 있음

  • CameraCharacteristics
    - 카메라의 정보를 담고 있는 객체
    - 전/후면, 플래시 지원여부 등등

  • CameraDevice
    - 카메라 객체

  • CameraRequest
    - 촬영이나 미리보기를 요청할 때 쓰이는 객체.
    - 카메라 설정 변경할 때도 사용

  • CameraCaptureSession
    - CaptureRequest를 보내고 카메라 하드웨어에서 결과를 받을 수 있는 세션.
    - 만들기 위해서는 camera device + surface 필요
    - surface는 camera device에서 사용 가능한 포맷과 크기가 일치하도록 미리 설정되어함.
    - 대상 surface는 SurfaceView, SurfaceTexture via Surface(SurfaceTexture), MediaCodec, MediaRecorder, Allocation, and ImageReader등 클래스들을 얻을 수 있음.
    - CameraCaptureSession은 자기를 만든 CameraDevice에 종속되며 Raw frame을 CameraDevice에 전달해준다. 

  • CaptureResult
    - CaptureRequest의 결과. 이미지의 메타데이터도 가져올 수 있음.

Camera2를 할때 알아야할 5가지 콜백)

  1. SurfaceTextureListener : TextureView 준비
  2. CameraDevice.StateCallback() : CameraDevice 상태 관리(open, close)
  3. CameraCaptureSession.StateCallback() : CameraCaptureSession 준비
  4. CameraCaptureSession.CaptureCallback : CameraCaptureSession으로부터 이미지 데이터를 캡쳐한다.
  5. ImageReader.OnImageAvailableListener : CameraCaptureSession으로부터 이미지 데이터를 Retrieving

이제 이 개념들을 하나의 흐름으로 정리하면 이렇게 된다.

 


- 구현

0. TextureView.SurfaceTextureListener를 이용해 TextureView를 준비하고 onSurfaceTextureAvailable()함수에서 카메라를 오픈한다.

private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
    override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
        openCamera()
    }

    override fun onSurfaceTextureSizeChanged(
        surface: SurfaceTexture,
        width: Int,
        height: Int
    ) {
    }

    override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
        return false
    }

    override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
}


1. 카메라의 모든 작업은 CameraManager부터 시작된다. CameraManager는 getSystemService()함수로 얻는다

val manager = getSystemService(Context.CAMERA_SERVICE) as CameraManager

 

 

2. CameraManager의 getCameraIdList() 함수를 호출하면 카메라 개수의 크기를 가진 카메라 식별자 배열을 얻는다.

val cameraId = manager.cameraIdList[1]


3. manager의 getCameraCharacteristics(cameraId)를 호출해 각 카메라의 정보를 갖는 cameraCharacteristics 객체를 얻는다

val cameraCharacteristics = manager.getCameraCharacteristics(cameraId) // 카메라의 정보를 담고있는 객체

 


4. CameraCharacteristics의 get(Key<T> key)함수에 키값으로 카메라의 정보를 얻어올수 있다.
예를들어 characteristics.get(characteristics.LENS_FACING)를 통해 카메라의 렌즈 방향에 대한 정보를 얻을수 있다.

if (characteristics.get(characteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) {...}

 

5. CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP 키값으로 StreamConfigurationMap 객체를 얻을 수 있는데, 이 객체에 카메라의 각종 지원 정보가 포함되어 있다. StreamConfigurationMap에서 얻는 정보 중 가장 중요한 정보는 카메라에서 지원하는 크기 목록이며 getOutputSizes() 함수에 이미지 포맷을 매개변수로 지정하면 지원하는 크기 목록이 Size 객체의 배열로 반환되는데, 이 값을 이용하여 사진 촬영 시 사진 크기를 지정할 수 있다.

val map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // StreamConfigurationMap 은 카메라의 크기나 방향과 같은 지원 정보를 포함
val sizesForStream = map?.getOutputSizes(SurfaceTexture::class.java) // 이미지 포맷을 매개변수로 넣으면 지원하는 크기목록의 객체 배열을 반환한다.

 

또한 val mSensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)을 이용해 카메라의 방향에 대한 정보를 얻을수 있다.

 

6. Manager.openCamera()로 카메라를 연다. 이때 들어가는게 카메라ID와 CameraDevice.StateCallback()이다. 콜백내 onOpend()함수에서 카메라를 받아오고 이때 카메라 프리뷰를 만들수 있다.

 

7. 위에서 정의한 Size를 이용해 setDefaultBufferSize(width,heigth)에 width, height를 넣어주고 Surface(texture)를 만들어준다.

val texture = binding.textureView.surfaceTexture
texture?.setDefaultBufferSize(mPreviewSize.width, mPreviewSize.height)

val textureViewSurface = Surface(texture)

 

8. OpenCamera에서 받아온 CameraDevice로 CaptureRequest를 만든다. 이때 CameraDevice에 있는 타입을 전달한다.
CaptureRequest는 파이프라인을 선택하여 CaptureSession에서 관리된다.

mCaptureRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
mCaptureRequestBuilder.addTarget(textureViewSurface)
mCaptureRequestBuilder.set(
    CaptureRequest.CONTROL_MODE,
    CameraMetadata.CONTROL_MODE_AUTO
)


9. OpenCamera에서 받아온 CameraDevice로 CaptureSession을 만든다. 이때 파라미터로 Surface의 리스트와 CameraCaptureSession.StateCallback 그리고 handler가 들어간다.

mCamera.createCaptureSession(
    listOf(textureViewSurface),
    object : CameraCaptureSession.StateCallback() {
        override fun onConfigured(session: CameraCaptureSession) {
            mCameraSession = session
            updatePreview()
        }

        override fun onConfigureFailed(session: CameraCaptureSession) {
            Log.e("onConfigureFailed", session.toString())
        }

    },
    null
)

 

10. CameraCaptureSession.StateCallback()의 onConfigured에서받아온 CameraCaptureSession으로 setRepeatingRequest()을 실행하여 Request를 끊임없이 보낸다.

mCameraSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null)

 

+ 풀소스)

더보기
class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding
    lateinit var mCamera: CameraDevice
    lateinit var mPreviewSize: Size
    lateinit var mCameraSession: CameraCaptureSession
    lateinit var mCaptureRequestBuilder: CaptureRequest.Builder

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }


    private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
        override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
            openCamera()
        }

        override fun onSurfaceTextureSizeChanged(
            surface: SurfaceTexture,
            width: Int,
            height: Int
        ) {
        }

        override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
            return false
        }

        override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
    }

    private fun openCamera() {
        val manager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
        val cameraId = manager.cameraIdList[1] // 카메라 개수만큼의 크기를 가진 카메라 식별자 배열
        val cameraCharacteristics = manager.getCameraCharacteristics(cameraId) // 카메라의 정보를 담고있는 객체
        val map =
            cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) // StreamConfigurationMap 은 카메라의 크기나 방향과 같은 지원 정보를 포함
        val sizesForStream = map?.getOutputSizes(SurfaceTexture::class.java) // 이미지 포맷을 매개변수로 넣으면 지원하는 크기목록의 객체 배열을 반환한다.
        val mSensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) // 카메라의 방향에 대한 정보

        if (ActivityCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }

        mPreviewSize = sizesForStream?.get(0) ?: Size(100, 100)

        try {
            manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
                override fun onOpened(camera: CameraDevice) {
                    mCamera = camera
                    showCameraPreview()
                }

                override fun onDisconnected(camera: CameraDevice) {
                    mCamera.close()
                }

                override fun onError(camera: CameraDevice, error: Int) {
                    mCamera.close()
                    Log.e("err", "$error")
                }

            }, null)
        } catch (e: CameraAccessException) {
            Log.e("err", e.toString())
        }
    }

    private fun showCameraPreview() {
        try {
            val texture = binding.textureView.surfaceTexture
            texture?.setDefaultBufferSize(mPreviewSize.width, mPreviewSize.height)

            val textureViewSurface = Surface(texture)

            mCaptureRequestBuilder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
            mCaptureRequestBuilder.addTarget(textureViewSurface)
            mCaptureRequestBuilder.set(
                CaptureRequest.CONTROL_MODE,
                CameraMetadata.CONTROL_MODE_AUTO
            )

            mCamera.createCaptureSession(
                listOf(textureViewSurface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigured(session: CameraCaptureSession) {
                        mCameraSession = session
                        updatePreview()
                    }

                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        Log.e("onConfigureFailed", session.toString())
                    }

                },
                null
            )
        } catch (e: CameraAccessException) {
            Log.e("err", e.toString())
        }
    }

    private fun updatePreview() {
        mCameraSession.setRepeatingRequest(mCaptureRequestBuilder.build(), null, null)
    }

    // 보통 카메라의 관한 셋팅은 onResume()에서 처리한다.
    override fun onResume() {
        super.onResume()
        binding.textureView.surfaceTextureListener = surfaceTextureListener
    }
}

출처 :

https://lucky516.tistory.com/177

https://proandroiddev.com/understanding-camera2-api-from-callbacks-part-1-5d348de65950

반응형

'Android' 카테고리의 다른 글

Android Paging3 개념정리 및 사용기  (0) 2022.07.06
안드로이드 Server’scertificate is not trusted 해결  (0) 2022.06.20
Android Room  (0) 2022.06.15
안드로이드 애니메이션  (0) 2022.06.08