본문 바로가기
Android/Android Core

[Android/Kotlin] CameraX로 자동 사진 및 비디오 촬영 기능 구현하기

by quessr 2024. 10. 25.

 

이번 글에서는 Android의 CameraX 라이브러리를 활용하여 자동으로 사진 및 비디오를 촬영하는 기능을 구현하는 방법을 소개합니다. 권한 설정부터 CameraX 의존성 추가, 미리보기 화면 구성, 자동 촬영 기능 구현까지 단계별로 설명해 보겠습니다. CameraX의 주요 기능을 이해하고 실제 프로젝트에서 어떻게 활용할 수 있는지 다뤄보겠습니다.

1. 권한 설정 (AndroidManifest.xml)

CameraX를 사용해 카메라와 오디오를 제어하려면 다음과 같은 권한이 필요합니다.

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
<uses-permission
    android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />
  • CAMERA: 카메라 사용을 위한 필수 권한입니다.
  • RECORD_AUDIO: 비디오 촬영 시 오디오 녹음을 위해 필요합니다.
  • WRITE_EXTERNAL_STORAGE 및 READ_EXTERNAL_STORAGE: Android 10 이하에서는 외부 저장소에 파일을 저장하려면 이 권한이 필요합니다. Android 11 이상에서는 MediaStore API를 사용하는 것이 권장되므로, maxSdkVersion 속성을 사용하여 특정 Android 버전까지만 요청합니다.

2. CameraX 의존성 추가

CameraX는 Android에서 카메라 기능을 쉽게 구현할 수 있게 해주는 라이브러리입니다. 다음 의존성을 build.gradle 파일에 추가합니다.

// CameraX 라이브러리
implementation("androidx.camera:camera-core:1.1.0-beta01")
implementation("androidx.camera:camera-camera2:1.1.0-beta01")
implementation("androidx.camera:camera-lifecycle:1.1.0-beta01")
implementation("androidx.camera:camera-video:1.1.0-beta01")
implementation("androidx.camera:camera-view:1.1.0-beta01")
implementation("androidx.camera:camera-extensions:1.1.0-beta01")

3. FileProvider 설정 추가

FileProvider는 외부 애플리케이션과 파일을 안전하게 공유하기 위해 필요한 설정입니다. 이를 설정하려면 다음과 같이 진행합니다.

 

1.AndroidManifest.xml 파일에 다음 코드를 추가합니다.

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

 

2. res/xml/file_paths.xml 파일을 생성하고, 파일 접근 경로를 정의합니다.

<paths>
    <external-path name="external_files" path="." />
</paths>


4. 미리보기 화면 구성

CameraX에서 미리보기 화면을 설정하려면 PreviewView를 레이아웃에 추가해야 합니다. XML 레이아웃 파일에 다음과 같이 설정합니다.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.camera.view.PreviewView
        android:id="@+id/viewFinder"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

이 코드에서는 PreviewView를 전체 화면에 걸쳐 배치하여 카메라 프리뷰를 표시할 수 있도록 설정합니다. PreviewView는 CameraX의 Preview 사용 케이스와 연동되어 실제 카메라 화면을 보여줍니다.

5. 자동 촬영 기능 구현

자동 촬영 기능은 사용자 설정에 따라 사진, 비디오, 또는 아무것도 촬영하지 않는 옵션을 제공하고, 설정된 옵션에 따라 자동으로 촬영을 실행합니다.

 

5.1 자동 촬영 시작 설정

Activity가 시작되면 권한을 확인한 후 자동 촬영을 실행할 수 있도록 startCamera() 메서드를 호출합니다. 권한이 있는 경우 UserSettingsManager를 통해 사용자가 선택한 촬영 형식을 설정하고 카메라를 시작합니다.

if (allPermissionsGranted()) {
    captureFormat = UserSettingsManager.getEmergencyFormat(this)
    startCamera()
} else {
    ActivityCompat.requestPermissions(
        this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
    )
}

 

5.2 startCamera() 메서드

startCamera() 메서드 내부에서 촬영 형식에 따라 자동으로 사진을 촬영하거나 비디오를 녹화합니다.

private fun startCamera() {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
    cameraProviderFuture.addListener({
        val cameraProvider = cameraProviderFuture.get()

        val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
            }

        imageCapture = ImageCapture.Builder().build()
        val recorder = Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.HIGHEST)).build()
        videoCapture = VideoCapture.withOutput(recorder)

        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

        try {
            cameraProvider.unbindAll()
            cameraProvider.bindToLifecycle(
                this, cameraSelector, preview, imageCapture, videoCapture
            )

            // 사용자가 선택한 촬영 형식에 따라 실행
            when (captureFormat) {
                UserSettingsManager.EmergencyFormatType.PHOTO -> takePhoto()
                UserSettingsManager.EmergencyFormatType.VIDEO -> captureVideo()
                else -> Log.d("MediaCaptureActivity", "No valid capture format")
            }
        } catch (exc: Exception) {
            Log.e("CameraXApp", "Use case binding failed", exc)
        }
    }, ContextCompat.getMainExecutor(this))
}

6. 사진 촬영 구현 (takePhoto())

takePhoto() 메서드는 ImageCapture 클래스를 사용하여 사진을 촬영하고 앱의 내부 저장소에 저장합니다.

private fun takePhoto() {
    val photoFile = File(
        getExternalFilesDir(Environment.DIRECTORY_PICTURES),
        SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.KOREA).format(System.currentTimeMillis()) + ".jpg"
    )

    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

    imageCapture?.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(this),
        object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Log.e("CameraXApp", "Photo capture failed: ${exc.message}", exc)
            }

            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                Toast.makeText(baseContext, "Photo capture succeeded: ${photoFile.absolutePath}", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    )
}

7. 비디오 촬영 구현 (captureVideo())

captureVideo() 메서드는 VideoCapture 클래스를 사용하여 비디오를 촬영하고 저장합니다.

private fun captureVideo() {
    val videoFile = File(getExternalFilesDir(Environment.DIRECTORY_MOVIES), "${SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US).format(System.currentTimeMillis())}.mp4")
    val outputOptions = FileOutputOptions.Builder(videoFile).build()

    recording = videoCapture?.output?.prepareRecording(this, outputOptions)?.apply {
        if (ActivityCompat.checkSelfPermission(this@MediaCaptureActivity, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) {
            withAudioEnabled()
        }
    }?.start(ContextCompat.getMainExecutor(this)) { recordEvent ->
        when (recordEvent) {
            is VideoRecordEvent.Start -> {
                Toast.makeText(this, "Recording started", Toast.LENGTH_SHORT).show()
                object : CountDownTimer(10_000, 1_000) {
                    override fun onTick(millisUntilFinished: Long) {}
                    override fun onFinish() {
                        recording?.stop()
                    }
                }.start()
            }
            is VideoRecordEvent.Finalize -> {
                if (!recordEvent.hasError()) {
                    Toast.makeText(this, "Video saved to ${videoFile.absolutePath}", Toast.LENGTH_SHORT).show()
                    finish()
                }
            }
        }
    }
}

 


참고: https://developer.android.com/media/camera/camerax/take-photo?_gl=1*p4bzdh*_up*MQ..*_ga*MTQzMDcxMzQxNi4xNzI5ODIwNDAw*_ga_6HH9YJMN9M*MTcyOTgyMDM5OS4xLjAuMTcyOTgyMDM5OS4wLjAuMjEwNzA5NzE0

 

이미지 캡처  |  Android media  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 이미지 캡처 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이미지 캡처 사용 사례는 고해상도 고화

developer.android.com

 

반응형