Computer >> 컴퓨터 >  >> 체계 >> Android

Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법

우리는 모두 휴대폰에서 카메라를 사용하고 그것을 사용합니다. 카메라를 기능으로 통합한 일부 응용 프로그램도 있습니다.

한쪽 끝에는 카메라와 상호 작용하는 표준 방식이 있습니다. 다른 한편으로는 카메라와의 상호 작용을 사용자 정의하는 방법이 있습니다. 이 구분은 중요합니다. 그리고 바로 Camera2가 필요합니다.

카메라2가 무엇인가요?

API 레벨 21부터 사용할 수 있었지만 Camera2 API는 개발자가 처리해야 하는 더 복잡한 아키텍처 중 하나여야 합니다.

이 API와 이전 API는 개발자가 응용 프로그램 내부에서 카메라와 상호 작용하는 기능을 활용할 수 있도록 배치되었습니다.

마이크 또는 장치의 볼륨과 상호 작용하는 방법과 유사하게 Camera2 API는 장치의 카메라와 상호 작용할 수 있는 도구를 제공합니다.

일반적으로 Camera2 API를 사용하려는 경우 단순히 사진을 찍거나 동영상을 녹화하는 것 이상일 것입니다. API를 사용하면 특정 기기별로 구성해야 하는 다양한 클래스를 노출하여 카메라를 심층적으로 제어할 수 있기 때문입니다.

이전에 카메라를 다루었더라도 이전 카메라 API에서 너무 과감한 변경이므로 알고 있는 모든 것을 잊어버릴 수도 있습니다.

이 API를 직접 사용하는 방법을 보여주려는 수많은 리소스가 있지만 그 중 일부는 구식일 수 있고 일부는 전체 그림을 나타내지 않을 수 있습니다.

따라서 누락된 부분을 혼자서 채우려고 하는 대신 이 기사가 Camera2 API와 상호작용할 수 있는 원스톱 쇼핑이 되기를 바랍니다.

Camera2 사용 사례

카메라를 사용하여 사진을 찍거나 비디오를 녹화하려는 경우 Camera2 API를 사용하지 않아도 된다는 점을 이해하는 것이 중요합니다.

Camera2 API를 사용하는 주된 이유는 애플리케이션에 카메라 또는 카메라 기능과의 일부 사용자 지정 상호 작용이 필요한 경우입니다.

후자 대신 전자를 수행하는 데 관심이 있다면 Google에서 다음 문서를 방문하는 것이 좋습니다.

  1. 사진 찍기
  2. 동영상 캡처

여기에서 카메라로 멋진 사진과 비디오를 캡처하는 데 필요한 모든 단계를 찾을 수 있습니다. 그러나 이 기사에서는 Camera2를 사용하는 방법에 중점을 둘 것입니다.

이제 매니페스트 파일에 추가해야 할 몇 가지 사항이 있습니다.

카메라 권한:

<uses-permission android:name="android.permission.CAMERA" />

카메라 기능:

<uses-feature android:name="android.hardware.camera" />

카메라 권한이 부여되었는지 여부를 확인해야 하지만 이 주제는 광범위하게 다루어졌기 때문에 이 기사에서는 다루지 않습니다.

Camera2 API 구성요소를 설정하는 방법

Camera2 API에는 몇 가지 새로운 인터페이스와 클래스가 도입되었습니다. 사용법을 더 잘 이해할 수 있도록 각각을 분해해 보겠습니다.

Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법
모든 구성요소 살펴보기

먼저 TextureView부터 시작하겠습니다.

Camera2 TextureView 구성 요소

TextureView는 콘텐츠 스트림(비디오 생각)을 표시하는 데 사용하는 UI 구성 요소입니다. 미리보기이든 사진/비디오를 찍기 전이든 카메라의 피드를 표시하려면 TextureView를 사용해야 합니다.

TextureView와 관련하여 사용하는 데 중요한 두 가지 속성은 다음과 같습니다.

  • SurfaceTexture 필드
  • SurfaceTextureListener 인터페이스

첫 번째는 콘텐츠가 표시되는 위치이고 두 번째는 4개의 콜백이 있습니다.

  1. onSurfaceTexture 사용 가능
  2. onSurfaceTextureSizeChanged
  3. onSurfaceTexture 업데이트됨
  4. onSurfaceTextureDestroyed
private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
        override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {

        }
        override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
        
        }
        
        override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) {
           
        }
        override fun onSurfaceTextureUpdated(texture: SurfaceTexture) {
          
        }
}

첫 번째 콜백은 카메라를 사용할 때 중요합니다. SurfaceTexture를 사용할 수 있을 때 알림을 받아 피드 표시를 시작할 수 있기 때문입니다.

TextureView가 창에 연결된 후에만 사용할 수 있다는 점에 유의하십시오.

카메라와의 상호 작용은 이전 API 이후 변경되었습니다. 이제 CameraManager가 있습니다. 이것은 CameraDevice 객체와 상호 작용할 수 있게 해주는 시스템 서비스입니다.

주의를 기울여야 하는 방법은 다음과 같습니다.

  • 오픈카메라
  • getCamera 특성
  • getCameraIdList

TextureView를 사용할 수 있고 준비가 되었으면 openCamera를 호출하여 카메라에 대한 연결을 열어야 합니다. 이 메서드는 세 가지 인수를 사용합니다.

  1. CameraId - 문자열
  2. CameraDevice.StateCallback
  3. 핸들러

CameraId 인수는 연결할 카메라를 나타냅니다. 휴대전화에는 주로 전면과 후면의 두 대의 카메라가 있습니다. 각각 고유한 ID가 있습니다. 일반적으로 0 또는 1입니다.

카메라 ID는 어떻게 얻나요? CameraManager의 getCamerasIdList 메소드를 사용합니다. 장치에서 식별된 모든 카메라 ID의 문자열 유형 배열을 반환합니다.

val cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraIds: Array<String> = cameraManager.cameraIdList
var cameraId: String = ""
for (id in cameraIds) {
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    //If we want to choose the rear facing camera instead of the front facing one
    if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      continue
    }
    
    val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    val imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, 1)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}

다음 인수는 카메라를 열려고 시도한 후 카메라 상태에 대한 콜백입니다. 생각해보면 이 행동에 대한 결과는 다음과 같습니다.

  • 카메라가 성공적으로 열립니다.
  • 카메라 연결 끊김
  • 일부 오류 발생

그리고 그것이 CameraDevice.StateCallback에서 찾을 수 있는 것입니다:

 private val cameraStateCallback = object : CameraDevice.StateCallback() {
        override fun onOpened(camera: CameraDevice) {
           
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
           
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            val errorMsg = when(error) {
                ERROR_CAMERA_DEVICE -> "Fatal (device)"
                ERROR_CAMERA_DISABLED -> "Device policy"
                ERROR_CAMERA_IN_USE -> "Camera in use"
                ERROR_CAMERA_SERVICE -> "Fatal (service)"
                ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
                else -> "Unknown"
            }
            Log.e(TAG, "Error when trying to connect camera $errorMsg")
        }
    }

세 번째 인수는 이 작업이 발생할 위치를 다룹니다. 메인 스레드를 점유하고 싶지 않기 때문에 이 작업은 백그라운드에서 하는 것이 좋습니다.

그렇기 때문에 Handler를 전달해야 합니다. 이 핸들러 인스턴스를 우리가 선택한 스레드로 인스턴스화하여 작업을 위임할 수 있도록 하는 것이 좋습니다.

private lateinit var backgroundHandlerThread: HandlerThread
private lateinit var backgroundHandler: Handler

 private fun startBackgroundThread() {
    backgroundHandlerThread = HandlerThread("CameraVideoThread")
    backgroundHandlerThread.start()
    backgroundHandler = Handler(
        backgroundHandlerThread.looper)
}

private fun stopBackgroundThread() {
    backgroundHandlerThread.quitSafely()
    backgroundHandlerThread.join()
}

지금까지 한 모든 작업을 통해 이제 openCamera를 호출할 수 있습니다.

cameraManager.openCamera(cameraId, cameraStateCallback,backgroundHandler)

그런 다음 onOpened 콜백을 사용하면 TextureView를 통해 사용자에게 카메라 피드를 표시하는 방법에 대한 논리를 처리할 수 있습니다.

Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법
Unsplash에 대한 Markus Spiske의 사진

피드 미리보기를 표시하는 방법

피드를 표시할 카메라(cameraDevice)와 TextureView가 있습니다. 그러나 피드의 미리보기를 표시할 수 있도록 서로 연결해야 합니다.

이를 위해 TextureView의 SurfaceTexture 속성을 사용하고 CaptureRequest를 빌드합니다.

val surfaceTexture : SurfaceTexture? = textureView.surfaceTexture // 1

val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) //2
val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!
  .getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!

surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height) //3

val previewSurface: Surface = Surface(surfaceTexture)

captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) //4
captureRequestBuilder.addTarget(previewSurface) //5

cameraDevice.createCaptureSession(listOf(previewSurface, imageReader.surface), captureStateCallback, null) //6
미리보기 만들기

위의 코드에서 먼저 TextureView에서 surfaceTexture를 가져옵니다. 그런 다음 cameraCharacteristics 객체를 사용하여 모든 출력 크기 목록을 가져옵니다. 원하는 크기를 얻으려면 surfaceTexture에 대해 설정합니다.

다음으로 TEMPLATE_PREVIEW를 전달하는 captureRequest를 만듭니다. . captureRequest에 입력 화면을 추가합니다.

마지막으로 입력 및 출력 화면인 captureStateCallback으로 captureSession을 시작하고 핸들러에 null을 전달합니다.

이 captureStateCallback은 무엇입니까? 이 기사 시작 부분의 다이어그램을 기억한다면 이것은 우리가 시작하는 CameraCaptureSession의 일부입니다. 이 객체는 다음 콜백으로 captureRequest의 진행 상황을 추적합니다.

  • 설정됨
  • 설정 실패
private val captureStateCallback = object : CameraCaptureSession.StateCallback() {
        override fun onConfigureFailed(session: CameraCaptureSession) {
            
        }
        override fun onConfigured(session: CameraCaptureSession) {
         
        }
}

cameraCaptureSession 성공적으로 구성되면 미리보기를 계속 표시할 수 있도록 세션에 대한 반복 요청을 설정합니다.

이를 위해 콜백에서 얻은 세션 객체를 사용합니다.

 session.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)

이 메서드에 대한 첫 번째 인수로 앞에서 만든 captureRequestBuilder 개체를 인식할 것입니다. 전달된 최종 매개변수가 CaptureRequest가 되도록 빌드 메소드를 제정합니다.

두 번째 인수는 CameraCaptureSession.captureCallback 리스너이지만 캡처된 이미지로 아무 것도 하고 싶지 않기 때문에(미리 보기이므로) null을 전달합니다.

세 번째 인수는 핸들러이며 여기에서는 자체 backgroundHandler를 사용합니다. 이것이 반복 요청이 백그라운드 스레드에서 실행되기 때문에 이전 섹션에서 null을 전달한 이유이기도 합니다.

Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법
Dicky Jiang의 Unsplash 사진

사진 찍는 방법

카메라를 실시간으로 미리 보는 것은 멋진 일이지만 대부분의 사용자는 카메라로 무언가를 하고 싶어할 것입니다. 사진을 찍기 위해 작성할 논리 중 일부는 이전 섹션에서 수행한 것과 유사합니다.

  1. captureRequest를 생성합니다
  2. ImageReader와 리스너를 사용하여 찍은 사진을 수집합니다.
  3. cameraCaptureSession을 사용하여 캡처 메소드를 호출합니다.
val orientations : SparseIntArray = SparseIntArray(4).apply {
    append(Surface.ROTATION_0, 0)
    append(Surface.ROTATION_90, 90)
    append(Surface.ROTATION_180, 180)
    append(Surface.ROTATION_270, 270)
}

val captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
captureRequestBuilder.addTarget(imageReader.surface)

val rotation = windowManager.defaultDisplay.rotation
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, orientations.get(rotation))
cameraCaptureSession.capture(captureRequestBuilder.build(), captureCallback, null)
이번에는 TEMPLATE_STILL_CAPTURE로 캡처 요청을 생성합니다.

그러나이 ImageReader는 무엇입니까? ImageReader는 표면에 렌더링되는 이미지 데이터에 대한 액세스를 제공합니다. 우리의 경우 TextureView의 표면입니다.

이전 섹션의 코드 스니펫을 보면 이미 ImageReader가 정의되어 있음을 알 수 있습니다.

val cameraManager: CameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraIds: Array<String> = cameraManager.cameraIdList
var cameraId: String = ""
for (id in cameraIds) {
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
    //If we want to choose the rear facing camera instead of the front facing one
    if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) 
      continue
    }
    
    val previewSize = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)!!.getOutputSizes(ImageFormat.JPEG).maxByOrNull { it.height * it.width }!!
    val imageReader = ImageReader.newInstance(previewSize.width, previewSize.height, ImageFormat.JPEG, 1)
    imageReader.setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
    cameraId = id
}
공지 라인 12 – 14

위에서 볼 수 있듯이 너비와 높이, 이미지를 포함할 이미지 형식 및 캡처할 수 있는 이미지 수를 전달하여 ImageReader를 인스턴스화합니다.

ImageReader 클래스의 속성은 onImageAvailableListener라는 수신기입니다. 이 리스너는 사진이 촬영되면 트리거됩니다(캡처 요청에 대한 출력 소스로 표면을 전달했기 때문에).

val onImageAvailableListener = object: ImageReader.OnImageAvailableListener{
        override fun onImageAvailable(reader: ImageReader) {
            val image: Image = reader.acquireLatestImage()
        }
    }

⚠️ 이미지를 처리한 후 닫으십시오. 그렇지 않으면 다른 사진을 찍을 수 없습니다.

Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법
Unsplash의 Jakob Owens 사진

동영상 녹화 방법

비디오를 녹화하려면 MediaRecorder라는 새 개체와 상호 작용해야 합니다. 미디어 레코더 개체는 오디오 및 비디오 녹음을 담당하며 우리는 이를 위해 사용할 것입니다.

무엇이든 하기 전에 미디어 레코더를 설정해야 합니다. 처리해야 할 다양한 구성이 있으며 올바른 순서로 구성되어 있어야 합니다. 그렇지 않으면 예외가 발생합니다. .

다음은 비디오(오디오 제외)를 캡처할 수 있는 구성 선택의 예입니다.

fun setupMediaRecorder(width: Int, height: Int) {
  val mediaRecorder: MediaRecorder = MediaRecorder()
  mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
  mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
  mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264)
  mediaRecorder.setVideoSize(videoSize.width, videoSize.height)
  mediaRecorder.setVideoFrameRate(30)
  mediaRecorder.setOutputFile(PATH_TO_FILE)
  mediaRecorder.setVideoEncodingBitRate(10_000_000)
  mediaRecorder.prepare()
}
세터의 순서가 중요합니다.

setOutputFile 에 주의하십시오. 비디오를 저장할 파일의 경로를 예상하는 메서드입니다. 이러한 모든 구성 설정이 끝나면 prepare를 호출해야 합니다.

mediaRecorder에도 start 메소드가 있으며 호출하기 전에 prepare를 호출해야 합니다.

mediaRecoder를 설정한 후 캡처 요청과 캡처 세션을 생성해야 합니다.

fun startRecording() {
        val surfaceTexture : SurfaceTexture? = textureView.surfaceTexture
        surfaceTexture?.setDefaultBufferSize(previewSize.width, previewSize.height)
        val previewSurface: Surface = Surface(surfaceTexture)
        val recordingSurface = mediaRecorder.surface
        captureRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD)
        captureRequestBuilder.addTarget(previewSurface)
        captureRequestBuilder.addTarget(recordingSurface)

        cameraDevice.createCaptureSession(listOf(previewSurface, recordingSurface), captureStateVideoCallback, backgroundHandler)
    }

미리보기를 설정하거나 사진을 찍는 것과 유사하게 입력 및 출력 표면을 정의해야 합니다.

여기서 TextureView의 surfaceTexture에서 Surface 개체를 만들고 미디어 레코더에서 표면을 가져옵니다. TEMPLATE_RECORD 를 전달 중입니다. 캡처 요청을 생성할 때의 값입니다.

captureStateVideoCallback은 스틸 사진에 사용한 것과 동일한 유형이지만 onConfigured 콜백 내에서 미디어 레코더의 시작 메소드를 호출합니다.

val captureStateVideoCallback = object : CameraCaptureSession.StateCallback() {
      override fun onConfigureFailed(session: CameraCaptureSession) {
         
      }
      
      override fun onConfigured(session: CameraCaptureSession) {
          session.setRepeatingRequest(captureRequestBuilder.build(), null, backgroundHandler)
          mediaRecorder.start()
      }
  }
여기에서는 연속적인 동영상을 캡처하기 위해 반복 요청도 설정하고 있습니다.

이제 우리는 비디오를 녹화하고 있습니다. 그러나 어떻게 녹화를 중지합니까? 이를 위해 mediaRecorder 객체에서 stop 및 reset 메소드를 사용할 것입니다.

mediaRecorder.stop()
mediaRecorder.reset()

결론

처리해야 할 일이 많았습니다. 여기까지 했다면 축하합니다! 우회할 수 있는 방법은 없습니다. 코드로 손을 더럽히는 것만으로도 모든 것이 어떻게 연결되는지 이해하기 시작할 것입니다.

아래 이 기사에 나오는 모든 코드를 살펴보는 것이 좋습니다.

MediumArticles/Camrea2API at master · TomerPacific/MediumArticles내가 작성한 다양한 Medium 기사와 관련된 코드를 포함하는 저장소 - MediumArticles/Camrea2API at master · TomerPacific/MediumArticles Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법 TomerPacificGitHub Android Camera2 – Camera2 API를 사용하여 사진과 동영상을 찍는 방법

Camera2 API와 관련하여 이것은 빙산의 일각에 불과합니다. 슬로우 모션 비디오 캡처, 전면 및 후면 카메라 전환, 초점 제어 등 다양한 작업을 수행할 수 있습니다.