카메라 API

Android 프레임워크에는 기기에서 이용 가능한 다양한 카메라와 카메라 기능에 관한 지원을 포함하여 애플리케이션에서 사진과 동영상을 캡처할 수 있도록 합니다. 이 문서에서는 신속하고 간단하게 이미지와 동영상을 캡처하는 방법을 논하고, 사용자를 위해 맞춤 카메라 환경을 만들기 위한 고급 단계를 개괄적으로 설명합니다.

참고: 이 페이지에서는 지원 중단된 Camera 클래스에 대해 설명합니다. CameraX Jetpack 라이브러리를 사용하거나, 특정 사용 사례에는 camera2 클래스를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상에서 작동합니다.

다음 관련 리소스를 참고하세요.

고려사항

Android 기기에서 카메라를 사용하도록 애플리케이션을 활성화하기 전에 앱이 이 하드웨어 기능을 어떻게 사용할 것인지 몇 가지 질문을 고민해 보아야 합니다.

  • 카메라 요구사항 - 카메라가 없는 기기에는 애플리케이션 설치를 원하지 않을 정도로 애플리케이션에 카메라 사용이 중요한가요? 이런 경우 매니페스트에 카메라 요구사항을 선언해야 합니다.
  • 빠른 사진 또는 맞춤설정 카메라 - 애플리케이션은 카메라를 어떻게 사용할 예정인가요? 사진이나 동영상을 빨리 찍는 데만 관심이 있으신가요? 아니면 애플리케이션에서 새로운 카메라 사용법을 제시할 생각인가요? 가벼운 스냅샷이나 영상 촬영용이라면 기존 카메라 앱 사용을 고려해보세요. 맞춤설정 카메라 기능을 개발하려면 카메라 앱 빌드 섹션을 참조하세요.
  • 포그라운드 서비스 요구사항 - 앱은 언제 카메라와 상호작용하나요? Android 9(API 수준 28) 이상에서는 백그라운드에서 실행되는 앱이 카메라에 액세스할 수 없습니다. 그러므로 앱이 포그라운드에 있거나 포그라운드 서비스에 포함될 때 카메라를 사용해야 합니다.
  • 저장소 - 애플리케이션에서 생성한 이미지나 동영상이 애플리케이션에만 표시되기를 원하시나요? 아니면 공유 가능하도록 설정하여 갤러리 등의 다른 애플리케이션 또는 기타 미디어 및 소셜 앱에서도 사용할 수 있게 할 생각인가요? 애플리케이션을 제거하더라도 사진과 동영상은 이용할 수 있게 하고 싶으신가요? 이러한 옵션을 구현하는 방법은 미디어 파일 저장 섹션을 참조하세요.

기본 사항

Android 프레임워크는 android.hardware.camera2 API 또는 카메라 Intent를 통해 이미지와 동영상 캡처를 지원합니다. 관련 클래스는 다음과 같습니다.

android.hardware.camera2
이 패키지는 기기 카메라를 제어하기 위한 기본 API입니다. 카메라 애플리케이션을 빌드할 때 사진이나 동영상을 촬영하는 데 사용할 수 있습니다.
Camera
이 클래스는 기기 카메라를 제어하는 API로, 이미 지원이 중단되었습니다.
SurfaceView
이 클래스는 사용자에게 실시간 카메라 미리보기를 보여주는 데 사용합니다.
MediaRecorder
이 클래스는 카메라에서 동영상을 녹화하는 데 사용합니다.
Intent
인텐트 작업 유형 MediaStore.ACTION_IMAGE_CAPTURE 또는 MediaStore.ACTION_VIDEO_CAPTURE를 사용하면 Camera 객체를 직접 사용하지 않고도 이미지나 동영상을 캡처할 수 있습니다.

매니페스트 선언

애플리케이션에서 Camera API를 사용해 개발 작업을 진행하기 전에, 우선 매니페스트에 카메라 하드웨어 및 관련 기능의 사용을 허용하는 적절한 선언이 포함되어 있어야 합니다.

  • 카메라 권한 - 애플리케이션이 기기 카메라를 사용할 권한을 요청해야 합니다.
    <uses-permission android:name="android.permission.CAMERA" />

    참고: 기존 카메라 앱을 호출하여 카메라를 사용하고 있는 경우, 애플리케이션이 이 권한을 요청하지 않아도 됩니다.

  • 카메라 기능 - 애플리케이션은 다음과 같은 카메라 기능의 사용도 선언해야 합니다.
    <uses-feature android:name="android.hardware.camera" />

    카메라 기능의 목록은 기능 참조 매니페스트를 참조하세요.

    매니페스트에 카메라 기능을 추가하면 Google Play가 애플리케이션이 카메라가 없는 기기 또는 개발자가 지정한 카메라 기능을 지원하지 않는 기기에는 설치되지 않도록 차단합니다. Google Play로 기능 기반 필터링을 사용하는 방법에 대한 자세한 내용은 Google Play 및 기능 기반 필터링을 참조하세요.

    애플리케이션이 적절한 작동을 위해 카메라나 카메라 기능을 사용할 수는 있지만 꼭 필요한 것은 아닌 경우, 이 내용을 매니페스트에 명시해야 합니다. 그러려면 android:required 속성을 포함하되 이를 false로 설정합니다.

    <uses-feature android:name="android.hardware.camera" android:required="false" />
  • 저장소 권한 - Android 10(API 수준 29) 이하를 대상으로 하고 매니페스트에 다음을 지정하는 애플리케이션은 기기의 외부 저장소(SD 카드)에 이미지 또는 동영상을 저장할 수 있습니다.
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • 오디오 녹음 권한 - 동영상을 캡처하는 동시에 오디오를 녹음하려면 애플리케이션이 오디오 캡처 권한을 요청해야 합니다.
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
  • 위치 권한 - 애플리케이션이 GPS 위치 정보로 이미지를 태그할 경우, ACCESS_FINE_LOCATION 권한을 요청해야 합니다. 참고로, 앱이 Android 5.0(API 수준 21) 이상을 대상으로 할 경우 앱이 기기의 GPS를 사용한다는 것도 선언해야 합니다.

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
    <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
    <uses-feature android:name="android.hardware.location.gps" />

    사용자 위치를 가져오는 방법에 관한 자세한 내용은 위치 전략을 참조하세요.

기존 카메라 앱 사용

애플리케이션에서 코드를 많이 추가하지 않고도 사진 또는 동영상 촬영을 활성화하는 간단한 방법은 Intent를 사용하여 기존 Android 카메라 애플리케이션을 호출하는 것입니다. 자세한 내용은 간단한 사진 촬영간단한 동영상 녹화 교육 과정에 설명되어 있습니다.

카메라 앱 빌드

일부 개발자는 애플리케이션의 디자인에 맞춤설정된 카메라 사용자 인터페이스가 필요하거나 특수 기능을 제공하는 사용자 인터페이스가 필요할 수 있습니다. 사진 촬영 코드를 직접 작성하면 사용자에게 더욱 매력적인 경험을 제공할 수 있습니다.

참고: 다음 가이드는 이전의 Camera API(지원 중단됨)에 적용되는 내용입니다. 신형 또는 고급 카메라 애플리케이션의 경우, 최신 android.hardware.camera2 API를 사용하는 것이 좋습니다.

애플리케이션에 맞춤설정 카메라 인터페이스를 만드는 일반적인 단계는 다음과 같습니다.

  • 카메라 감지 및 액세스 - 카메라 유무를 확인하여 액세스를 요청하는 코드를 작성합니다.
  • 미리보기 클래스 만들기 - SurfaceView를 펼치고 SurfaceHolder 인터페이스를 구현하는 카메라 미리보기 클래스를 만듭니다. 이 클래스는 카메라에서 가져온 실시간 이미지를 미리 보여줍니다.
  • 미리보기 레이아웃 빌드 - 카메라 미리보기 클래스를 만들었다면 미리보기와 원하는 사용자 인터페이스 컨트롤을 통합하는 뷰 레이아웃을 만듭니다.
  • 캡처를 위한 리스너 설정 - 사용자의 작업(예: 버튼 누르기)에 대응하여 이미지 또는 동영상 캡처를 시작하기 위해 인터페이스 컨트롤에 리스너를 연결합니다.
  • 캡처 및 파일 저장 - 사진 또는 동영상을 캡처하여 출력된 데이터를 저장하기 위한 코드를 설정합니다.
  • 카메라 해제 - 카메라를 사용한 다음, 애플리케이션이 이를 적절히 해제하여 다른 애플리케이션에서 사용할 수 있도록 해야 합니다.

카메라 하드웨어는 세심하게 관리해야 하는 공유 리소스이므로, 애플리케이션이 카메라를 사용하고자 하는 다른 애플리케이션과 충돌하지 않도록 유의해야 합니다. 다음 섹션에서는 카메라 하드웨어 감지 방법, 카메라 액세스를 요청하는 방법, 사진 또는 동영상을 캡처하는 방법, 애플리케이션에서 카메라 사용이 끝난 후 카메라를 해제하는 방법 등을 논합니다.

주의: 애플리케이션이 Camera 객체를 사용 완료한 후에는 Camera.release()를 호출하여 이 객체를 해제하는 것을 잊지 마시기 바랍니다. 애플리케이션이 카메라를 적절히 해제하지 않으면 이후에 카메라에 액세스하려는 모든 시도(본인의 애플리케이션이 요청한 것도 포함)가 실패하고 본인의 애플리케이션이나 다른 애플리케이션이 종료될 수 있습니다.

카메라 하드웨어 감지

애플리케이션에 꼭 매니페스트 선언을 사용하는 카메라가 필요한 것이 아니라면, 런타임에 카메라를 사용할 수 있는지 확인해야 합니다. 이 검사를 하려면 PackageManager.hasSystemFeature() 메서드를 사용합니다. 아래의 예시 코드를 참조하세요.

Kotlin

/** Check if this device has a camera */
private fun checkCameraHardware(context: Context): Boolean {
    if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
        // this device has a camera
        return true
    } else {
        // no camera on this device
        return false
    }
}

자바

/** Check if this device has a camera */
private boolean checkCameraHardware(Context context) {
    if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
        // this device has a camera
        return true;
    } else {
        // no camera on this device
        return false;
    }
}

Android 기기에는 사진 촬영을 위한 후면 카메라, 영상 통화용 전면 카메라와 같은 여러 개의 카메라가 있을 수 있습니다. Android 2.3(API 수준 9) 이상에서는 Camera.getNumberOfCameras() 메서드를 사용하여 기기에서 사용 가능한 카메라 개수를 확인할 수 있습니다.

카메라 액세스

애플리케이션을 실행하는 기기에 카메라가 있다는 것을 확인했으면 Camera의 인스턴스를 가져와 카메라 액세스를 요청해야 합니다(인텐트를 사용하여 카메라에 액세스하는 경우는 예외).

기본 카메라에 액세스하려면 Camera.open() 메서드를 사용하되 예외를 모두 잡아내야 합니다. 아래 코드를 참조하세요.

Kotlin

/** A safe way to get an instance of the Camera object. */
fun getCameraInstance(): Camera? {
    return try {
        Camera.open() // attempt to get a Camera instance
    } catch (e: Exception) {
        // Camera is not available (in use or does not exist)
        null // returns null if camera is unavailable
    }
}

자바

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

주의: Camera.open()을 사용할 때는 항상 예외를 확인하세요. 카메라가 사용 중이거나 존재하지 않는지 등의 예외를 확인하지 못하면 시스템이 애플리케이션을 종료할 수 있습니다.

Android 2.3(API 수준 9) 이상을 실행하는 기기에서는 Camera.open(int)을 사용하여 특정 카메라에 액세스할 수 있습니다. 위의 예시 코드에서는 카메라가 두 대 이상인 기기에서 우선 후면 카메라부터 액세스합니다.

카메라 기능 확인

카메라 액세스를 획득했으면 Camera.getParameters() 메서드를 사용하여 카메라의 기능에 관한 자세한 정보를 알아보고, 반환된 Camera.Parameters 객체를 확인하면 지원되는 기능을 알 수 있습니다. API 수준 9 이상을 사용할 경우 Camera.getCameraInfo()를 사용하면 카메라 위치(기기의 앞면 또는 뒷면)과 이미지 방향을 판별할 수 있습니다.

미리보기 클래스 만들기

사용자가 효과적으로 사진이나 동영상을 촬영하려면 기기 카메라에 보이는 것을 볼 수 있어야 합니다. 카메라 미리보기 클래스는 SurfaceView로서 카메라에서 유입되는 실시간 이미지 데이터를 표시하므로, 사용자가 사진이나 동영상을 프레이밍하고 캡처할 수 있습니다.

다음 예시 코드는 View 레이아웃에 포함할 수 있는 기본 카메라 미리보기 클래스를 생성하는 방법을 나타냅니다. 이 클래스는 SurfaceHolder.Callback을 구현하여 뷰를 생성하고 제거하는 콜백 이벤트를 캡처하며, 카메라 미리보기 입력을 할당하는 데 필요합니다.

Kotlin

/** A basic Camera preview class */
class CameraPreview(
        context: Context,
        private val mCamera: Camera
) : SurfaceView(context), SurfaceHolder.Callback {

    private val mHolder: SurfaceHolder = holder.apply {
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        addCallback(this@CameraPreview)
        // deprecated setting, but required on Android versions prior to 3.0
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        mCamera.apply {
            try {
                setPreviewDisplay(holder)
                startPreview()
            } catch (e: IOException) {
                Log.d(TAG, "Error setting camera preview: ${e.message}")
            }
        }
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.surface == null) {
            // preview surface does not exist
            return
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview()
        } catch (e: Exception) {
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        mCamera.apply {
            try {
                setPreviewDisplay(mHolder)
                startPreview()
            } catch (e: Exception) {
                Log.d(TAG, "Error starting camera preview: ${e.message}")
            }
        }
    }
}

자바

/** A basic Camera preview class */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private SurfaceHolder mHolder;
    private Camera mCamera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        // deprecated setting, but required on Android versions prior to 3.0
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // empty. Take care of releasing the Camera preview in your activity.
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (mHolder.getSurface() == null){
          // preview surface does not exist
          return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e){
          // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();

        } catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

카메라 미리보기를 특정 크기로 설정하려면 위의 설명에서 언급한 것과 같이 surfaceChanged() 메서드에서 설정하면 됩니다. 미리보기 크기를 설정할 때는 반드시 getSupportedPreviewSizes() 값을 사용해야 합니다. setPreviewSize() 메서드에 임의의 값을 설정하면 안 됩니다.

참고: Android 7.0(API 수준 24) 이상에서 멀티 윈도우 기능이 도입되면서 미리보기의 가로세로 비율은 setDisplayOrientation()를 호출한 이후에도 Activity와 동일한 것으로 간주되지 않습니다. 창 크기와 가로세로 비율에 따라 레터박스 레이아웃을 사용하여 넓은 카메라 미리보기를 세로 방향 레이아웃에 끼워 넣거나, 그 반대의 경우가 될 수도 있습니다.

레이아웃에 미리보기 배치

위의 섹션에서 보여준 예시와 같이, 카메라 미리보기 클래스는 사진이나 동영상을 촬영하는 다른 사용자 인터페이스와 함께 Activity 레이아웃에 배치해야 합니다. 이 섹션에서는 미리보기의 기본 레이아웃과 Activity를 빌드하는 방법을 보여줍니다.

다음의 레이아웃 코드는 카메라 미리보기를 표시하는 데 사용할 수 있는 매우 기본적인 뷰를 제공합니다. 이 예시에서는 FrameLayout 요소가 카메라 미리보기 클래스의 컨테이너입니다. 이 레이아웃 유형을 사용한 것은 추가적인 사진 정보 또는 컨트롤을 실시간 카메라 미리보기 이미지에 오버레이하기 위해서입니다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
  <FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="1"
    />

  <Button
    android:id="@+id/button_capture"
    android:text="Capture"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
</LinearLayout>

대부분의 기기에서 카메라 미리보기의 기본 방향은 가로입니다. 이 예시 레이아웃에서는 수평(가로) 레이아웃을 지정하고 아래의 코드에서 애플리케이션의 방향을 가로로 고정합니다. 카메라 미리보기를 렌더링하는 작업을 단순화하려면 매니페스트에 다음과 같은 내용을 추가하여 애플리케이션의 미리보기 Activity 방향을 가로로 변경해야 합니다.

<activity android:name=".CameraActivity"
          android:label="@string/app_name"

          android:screenOrientation="landscape">
          <!-- configure this activity to use landscape orientation -->

          <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

참고: 카메라 미리보기가 가로 모드일 필요는 없습니다. Android 2.2(API 수준 8) 이상부터 setDisplayOrientation() 메서드를 사용하여 미리보기 이미지의 방향을 설정할 수 있습니다. 사용자가 전화기 방향을 바꿀 때 미리보기 방향을 바꾸려면 미리보기 클래스의 surfaceChanged() 메서드 내에서 우선 Camera.stopPreview()로 미리보기를 중지하고, 방향을 변경한 다음 Camera.startPreview()로 미리보기를 다시 시작하면 됩니다.

카메라 뷰의 활동에서 위의 예시에 표시된 FrameLayout 요소에 미리보기 클래스를 추가합니다. 또한 카메라가 일시중지되거나 종료되면 카메라 Activity에서 카메라를 해제해야 합니다. 다음 예시는 미리보기 클래스 만들기에서 보여준 미리보기 클래스를 첨부하도록 카메라 활동을 수정하는 방법을 보여줍니다.

Kotlin

class CameraActivity : Activity() {

    private var mCamera: Camera? = null
    private var mPreview: CameraPreview? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create an instance of Camera
        mCamera = getCameraInstance()

        mPreview = mCamera?.let {
            // Create our Preview view
            CameraPreview(this, it)
        }

        // Set the Preview view as the content of our activity.
        mPreview?.also {
            val preview: FrameLayout = findViewById(R.id.camera_preview)
            preview.addView(it)
        }
    }
}

자바

public class CameraActivity extends Activity {

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Create an instance of Camera
        mCamera = getCameraInstance();

        // Create our Preview view and set it as the content of our activity.
        mPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mPreview);
    }
}

참고: 위의 예에 나온 getCameraInstance() 메서드는 카메라 액세스에 나온 예시 메서드를 나타냅니다.

사진 캡처

미리보기 클래스와 이를 표시할 뷰 레이아웃을 빌드하고 나면, 이제 애플리케이션으로 이미지를 캡처할 수 있습니다. 애플리케이션 코드에서 사용자 인터페이스 컨트롤이 사용자의 작업에 반응해 사진을 촬영하도록 리스너를 설정해야 합니다.

사진을 가져오려면 Camera.takePicture() 메서드를 사용합니다. 이 메서드는 카메라에서 데이터를 수신하는 매개변수 세 개를 사용합니다. 데이터를 JPEG 형식으로 수신하려면 Camera.PictureCallback 인터페이스를 구현하여 이미지 데이터를 수신해서 파일에 작성하도록 해야 합니다. 다음 코드는 카메라에서 수신한 이미지를 저장하기 위한 Camera.PictureCallback 인터페이스의 기본 구현을 보여줍니다.

Kotlin

private val mPicture = Camera.PictureCallback { data, _ ->
    val pictureFile: File = getOutputMediaFile(MEDIA_TYPE_IMAGE) ?: run {
        Log.d(TAG, ("Error creating media file, check storage permissions"))
        return@PictureCallback
    }

    try {
        val fos = FileOutputStream(pictureFile)
        fos.write(data)
        fos.close()
    } catch (e: FileNotFoundException) {
        Log.d(TAG, "File not found: ${e.message}")
    } catch (e: IOException) {
        Log.d(TAG, "Error accessing file: ${e.message}")
    }
}

자바

private PictureCallback mPicture = new PictureCallback() {

    @Override
    public void onPictureTaken(byte[] data, Camera camera) {

        File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (pictureFile == null){
            Log.d(TAG, "Error creating media file, check storage permissions");
            return;
        }

        try {
            FileOutputStream fos = new FileOutputStream(pictureFile);
            fos.write(data);
            fos.close();
        } catch (FileNotFoundException e) {
            Log.d(TAG, "File not found: " + e.getMessage());
        } catch (IOException e) {
            Log.d(TAG, "Error accessing file: " + e.getMessage());
        }
    }
};

Camera.takePicture() 메서드를 호출해서 이미지 캡처를 트리거합니다. 다음 예시 코드는 View.OnClickListener 버튼에서 이 메서드를 호출하는 방법을 보여줍니다.

Kotlin

val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    // get an image from the camera
    mCamera?.takePicture(null, null, picture)
}

자바

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(R.id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // get an image from the camera
            mCamera.takePicture(null, null, picture);
        }
    }
);

참고: 다음 예의 mPicture 멤버는 위의 예시 코드를 나타냅니다.

주의: 애플리케이션이 Camera 객체를 사용 완료한 후에는 Camera.release()를 호출하여 이 객체를 해제하는 것을 잊지 마시기 바랍니다. 카메라를 해제하는 방법에 관한 자세한 내용은 카메라 해제를 참조하세요.

동영상 캡처

Android 프레임워크를 사용하여 동영상을 캡처하려면 Camera 객체를 신중하게 관리하고 MediaRecorder 클래스와 조정해야 합니다. Camera로 동영상을 촬영할 때는 Camera.open()Camera.release() 호출 외에도 Camera.lock()Camera.unlock() 호출을 관리하여 MediaRecorder가 카메라 하드웨어에 액세스하도록 허용해야 합니다.

참고: Android 4.0(API 수준 14) 이상부터 Camera.lock()Camera.unlock() 호출은 자동으로 관리됩니다.

기기 카메라로 사진을 촬영하는 것과 달리, 동영상을 캡처하려면 매우 특정한 호출 순서가 필요합니다. 애플리케이션으로 동영상을 캡처할 준비를 하고 성공적으로 캡처하려면 특정한 실행 순서를 따라야 합니다. 자세한 내용은 아래와 같습니다.

  1. 카메라 열기 - Camera.open()을 사용하여 카메라 객체의 인스턴스를 가져옵니다.
  2. 미리보기 연결 - Camera.setPreviewDisplay()를 사용하여 SurfaceView를 카메라에 연결하고 실시간 카메라 이미지 미리보기를 준비합니다.
  3. 미리보기 시작 - Camera.startPreview()를 호출하여 실시간 카메라 이미지를 표시하기 시작합니다.
  4. 동영상 녹화 시작 - 동영상을 성공적으로 기록하려면 다음 단계를 순서대로 완료해야 합니다.
    1. 카메라 잠금 해제 - Camera.unlock()을 호출하여 MediaRecorder에서 사용할 카메라의 잠금을 해제합니다.
    2. MediaRecorder 구성 - 다음 MediaRecorder 메서드를 이 순서대로 호출합니다. 자세한 내용은 MediaRecorder 참고 문서를 참조하세요.
      1. setCamera() - 카메라를 동영상 캡처에 사용하도록 설정합니다. 애플리케이션의 현재 Camera 인스턴스를 사용합니다.
      2. setAudioSource() - MediaRecorder.AudioSource.CAMCORDER를 사용하여 오디오 소스를 설정합니다.
      3. setVideoSource() - MediaRecorder.VideoSource.CAMERA를 사용하여 동영상 소스를 설정합니다.
      4. 동영상 출력 형식과 인코딩을 설정합니다. Android 2.2(API 수준 8) 이상의 경우, MediaRecorder.setProfile 메서드를 사용하고 CamcorderProfile.get()으로 프로필 인스턴스를 가져옵니다. Android 2.2 이하 버전의 경우 동영상 출력 형식과 인코딩 매개변수를 설정해야 합니다.
        1. setOutputFormat() - 기본값 설정 또는 MediaRecorder.OutputFormat.MPEG_4를 지정하여 출력 형식을 설정합니다.
        2. setAudioEncoder() - 기본값 설정 또는 MediaRecorder.AudioEncoder.AMR_NB를 지정하여 사운드 인코딩 유형을 설정합니다.
        3. setVideoEncoder() - 기본값 설정 또는 MediaRecorder.VideoEncoder.MPEG_4_SP를 지정하여 비디오 인코딩 유형을 설정합니다.
      5. setOutputFile() - 미디어 파일 저장 섹션에 나오는 예시 메서드의 getOutputMediaFile(MEDIA_TYPE_VIDEO).toString()을 사용하여 출력 파일을 설정합니다.
      6. setPreviewDisplay() - 애플리케이션에 대한 SurfaceView 미리보기 레이아웃 요소를 지정합니다. 미리보기 연결에 지정한 것과 동일한 객체를 사용합니다.

      주의:MediaRecorder 구성 메서드는 반드시 이 순서대로 호출해야 하며, 그러지 않으면 애플리케이션에 오류가 발생하고 녹화가 실패합니다.

    3. MediaRecorder 준비 - MediaRecorder.prepare()를 호출하여 제공된 구성 설정으로 MediaRecorder를 준비합니다.
    4. MediaRecorder 시작 - MediaRecorder.start()를 호출하여 동영상 녹화를 시작합니다.
  5. 동영상 녹화 중지 - 동영상 녹화를 성공적으로 완료하려면 다음 메서드를 순서대로 호출해야 합니다.
    1. MediaRecorder 중지 - MediaRecorder.stop()을 호출하여 동영상 녹화를 중지합니다.
    2. MediaRecorder 재설정 - 또는 MediaRecorder.reset()을 호출하여 녹음기에서 구성 설정을 삭제합니다.
    3. MediaRecorder 해제 - MediaRecorder.release()를 호출하여 MediaRecorder를 해제합니다.
    4. 카메라 잠금 - 향후 MediaRecorder 세션에서 카메라를 사용할 수 있도록 Camera.lock()을 호출하여 카메라를 잠급니다. Android 4.0(API 수준 14)부터는 MediaRecorder.prepare() 호출이 실패하지 않는 한 이 호출을 사용할 필요가 없습니다.
  6. 미리보기 중지 - Activity에서 카메라 사용을 완료하면 Camera.stopPreview()를 사용하여 미리보기를 중지합니다.
  7. 카메라 해제 - 다른 애플리케이션이 카메라를 사용할 수 있도록 Camera.release()를 호출하여 카메라를 해제합니다.

참고: 먼저 카메라 미리보기를 만들지 않고 이 절차의 처음 몇 단계를 건너뛰어 MediaRecorder를 사용하는 것도 가능합니다. 그러나 사용자는 보통 녹화를 시작하기 전에 미리보기를 확인하고 싶어하기 때문에 여기서는 그 절차를 언급하지 않습니다.

팁: 애플리케이션이 보통 동영상 녹화에 사용된다면, 미리보기를 시작하기 전에 setRecordingHint(boolean)true로 설정하세요. 이렇게 설정하면 녹화를 시작하는 데 걸리는 시간을 단축할 수 있습니다.

MediaRecorder 구성

MediaRecorder 클래스를 사용하여 동영상을 녹화할 때는 특정 순서에 따라 구성 단계를 실행한 다음, MediaRecorder.prepare() 메서드를 호출하여 구성을 확인하고 구현해야 합니다. 다음의 예시 코드는 동영상 녹화를 위해 MediaRecorder 클래스를 적절히 구성하고 준비하는 방법을 보여줍니다.

Kotlin

private fun prepareVideoRecorder(): Boolean {
    mediaRecorder = MediaRecorder()

    mCamera?.let { camera ->
        // Step 1: Unlock and set camera to MediaRecorder
        camera?.unlock()

        mediaRecorder?.run {
            setCamera(camera)

            // Step 2: Set sources
            setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
            setVideoSource(MediaRecorder.VideoSource.CAMERA)

            // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
            setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH))

            // Step 4: Set output file
            setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString())

            // Step 5: Set the preview output
            setPreviewDisplay(mPreview?.holder?.surface)

            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
            setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)


            // Step 6: Prepare configured MediaRecorder
            return try {
                prepare()
                true
            } catch (e: IllegalStateException) {
                Log.d(TAG, "IllegalStateException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            } catch (e: IOException) {
                Log.d(TAG, "IOException preparing MediaRecorder: ${e.message}")
                releaseMediaRecorder()
                false
            }
        }

    }
    return false
}

자바

private boolean prepareVideoRecorder(){

    mCamera = getCameraInstance();
    mediaRecorder = new MediaRecorder();

    // Step 1: Unlock and set camera to MediaRecorder
    mCamera.unlock();
    mediaRecorder.setCamera(mCamera);

    // Step 2: Set sources
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

    // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
    mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH));

    // Step 4: Set output file
    mediaRecorder.setOutputFile(getOutputMediaFile(MEDIA_TYPE_VIDEO).toString());

    // Step 5: Set the preview output
    mediaRecorder.setPreviewDisplay(mPreview.getHolder().getSurface());

    // Step 6: Prepare configured MediaRecorder
    try {
        mediaRecorder.prepare();
    } catch (IllegalStateException e) {
        Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    } catch (IOException e) {
        Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
        releaseMediaRecorder();
        return false;
    }
    return true;
}

Android 2.2(API 수준 8) 이전에는 CamcorderProfile을 사용하지 않고 출력 형식과 인코딩 형식 매개변수를 직접 설정해야 했습니다. 이 방법은 아래의 코드에서 확인할 수 있습니다.

Kotlin

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder?.apply {
        setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
        setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT)
        setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT)
    }

자바

    // Step 3: Set output format and encoding (for versions prior to API Level 8)
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);

다음 동영상 녹화 매개변수는 MediaRecorder용으로, 기본 설정이 부여되지만 애플리케이션에 따라 이 설정을 조정해야 할 수도 있습니다.

MediaRecorder 시작 및 중지

MediaRecorder 클래스를 사용하여 비디오 녹화를 시작하고 중지할 때는 아래와 같이 특정 순서를 따라야 합니다.

  1. Camera.unlock()으로 카메라 잠금 해제
  2. 위의 코드 예시와 같이 MediaRecorder 구성
  3. MediaRecorder.start()를 사용하여 녹화 시작
  4. 동영상 녹화
  5. MediaRecorder.stop()을 사용하여 녹화 중지
  6. MediaRecorder.release()로 미디어 녹음기 해제
  7. Camera.lock()을 사용하여 카메라 잠금

다음의 예시 코드는 카메라와 MediaRecorder 클래스를 사용하여 동영상 녹화를 적절히 시작하고 중지하기 위한 버튼을 연결하는 방법을 보여줍니다.

참고: 동영상 녹화가 완료되어도 카메라를 해제하지 마세요. 미리보기가 중지됩니다.

Kotlin

var isRecording = false
val captureButton: Button = findViewById(R.id.button_capture)
captureButton.setOnClickListener {
    if (isRecording) {
        // stop recording and release camera
        mediaRecorder?.stop() // stop the recording
        releaseMediaRecorder() // release the MediaRecorder object
        mCamera?.lock() // take camera access back from MediaRecorder

        // inform the user that recording has stopped
        setCaptureButtonText("Capture")
        isRecording = false
    } else {
        // initialize video camera
        if (prepareVideoRecorder()) {
            // Camera is available and unlocked, MediaRecorder is prepared,
            // now you can start recording
            mediaRecorder?.start()

            // inform the user that recording has started
            setCaptureButtonText("Stop")
            isRecording = true
        } else {
            // prepare didn't work, release the camera
            releaseMediaRecorder()
            // inform user
        }
    }
}

자바

private boolean isRecording = false;

// Add a listener to the Capture button
Button captureButton = (Button) findViewById(id.button_capture);
captureButton.setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (isRecording) {
                // stop recording and release camera
                mediaRecorder.stop();  // stop the recording
                releaseMediaRecorder(); // release the MediaRecorder object
                mCamera.lock();         // take camera access back from MediaRecorder

                // inform the user that recording has stopped
                setCaptureButtonText("Capture");
                isRecording = false;
            } else {
                // initialize video camera
                if (prepareVideoRecorder()) {
                    // Camera is available and unlocked, MediaRecorder is prepared,
                    // now you can start recording
                    mediaRecorder.start();

                    // inform the user that recording has started
                    setCaptureButtonText("Stop");
                    isRecording = true;
                } else {
                    // prepare didn't work, release the camera
                    releaseMediaRecorder();
                    // inform user
                }
            }
        }
    }
);

참고: 위의 예에서 prepareVideoRecorder() 메서드는 MediaRecorder 구성에 표시된 예시 코드를 나타냅니다. 이 메서드는 카메라 잠금, MediaRecorder 인스턴스의 구성 및 준비를 관리합니다.

카메라 해제

카메라는 한 대의 기기에서 여러 애플리케이션이 공유하는 리소스입니다. Camera 인스턴스를 가져왔으면 애플리케이션에서 카메라를 사용할 수 있습니다. 애플리케이션에서 더 이상 카메라를 사용하지 않거나 애플리케이션이 일시중지된 경우(Activity.onPause()) 즉시 주의해서 카메라 객체를 해제해야 합니다. 애플리케이션이 카메라를 적절히 해제하지 않으면 이후에 카메라에 액세스하려는 모든 시도(본인의 애플리케이션이 요청한 것도 포함)가 실패하고 본인의 애플리케이션이나 다른 애플리케이션이 종료될 수 있습니다.

Camera 객체의 인스턴스를 해제하려면 Camera.release() 메서드를 사용합니다. 아래의 예시 코드를 참조하세요.

Kotlin

class CameraActivity : Activity() {
    private var mCamera: Camera?
    private var preview: SurfaceView?
    private var mediaRecorder: MediaRecorder?

    override fun onPause() {
        super.onPause()
        releaseMediaRecorder() // if you are using MediaRecorder, release it first
        releaseCamera() // release the camera immediately on pause event
    }

    private fun releaseMediaRecorder() {
        mediaRecorder?.reset() // clear recorder configuration
        mediaRecorder?.release() // release the recorder object
        mediaRecorder = null
        mCamera?.lock() // lock camera for later use
    }

    private fun releaseCamera() {
        mCamera?.release() // release the camera for other applications
        mCamera = null
    }
}

자바

public class CameraActivity extends Activity {
    private Camera mCamera;
    private SurfaceView preview;
    private MediaRecorder mediaRecorder;

    ...

    @Override
    protected void onPause() {
        super.onPause();
        releaseMediaRecorder();       // if you are using MediaRecorder, release it first
        releaseCamera();              // release the camera immediately on pause event
    }

    private void releaseMediaRecorder(){
        if (mediaRecorder != null) {
            mediaRecorder.reset();   // clear recorder configuration
            mediaRecorder.release(); // release the recorder object
            mediaRecorder = null;
            mCamera.lock();           // lock camera for later use
        }
    }

    private void releaseCamera(){
        if (mCamera != null){
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }
}

주의: 애플리케이션이 카메라를 적절히 해제하지 않으면 이후에 카메라에 액세스하려는 모든 시도(본인의 애플리케이션이 요청한 것도 포함)가 실패하고 본인의 애플리케이션이나 다른 애플리케이션이 종료될 수 있습니다.

미디어 파일 저장

사진 및 동영상 등 사용자가 만든 미디어 파일을 기기의 외부 저장소 디렉터리(SD 카드)에 저장하여 시스템 공간을 절약하고 기기가 없어도 사용자가 해당 파일에 액세스할 수 있도록 해야 합니다. 기기에는 미디어 파일을 저장할 수 있는 디렉터리 위치가 많지만, 개발자로서 고민해야 할 표준 위치는 두 개뿐입니다.

  • Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - 이 메서드는 사진과 동영상 저장을 위한 기본, 공유 및 권장 위치를 반환합니다. 이 디렉터리는 공유(공개) 디렉터리이므로 다른 애플리케이션이 이 위치에 저장된 파일을 쉽게 검색하고 읽고 변경하고 삭제할 수 있습니다. 사용자가 애플리케이션을 제거해도 이 위치에 저장한 미디어 파일은 삭제되지 않습니다. 사용자의 기존 사진 및 동영상에 방해가 되지 않도록 하려면 이 디렉터리 내에 애플리케이션의 미디어 파일을 저장할 하위 디렉터리를 만드는 것이 좋습니다. 아래의 코드 샘플을 참조하세요. 이 메서드는 Android 2.2(API 수준 8)에서 이용할 수 있습니다. 이전 API 버전의 동급 호출은 공유된 파일 저장을 참조하세요.
  • Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) - 이 메서드는 애플리케이션과 연결된 사진 및 동영상 저장용 기본 위치를 반환합니다. 애플리케이션이 제거되면 이 위치에 저장된 모든 파일도 삭제됩니다. 이 위치의 파일에는 보안이 적용되지 않으므로 다른 애플리케이션이 이를 읽고 변경하고 삭제할 수 있습니다.

다음의 예시 코드는 Intent를 사용하거나 카메라 앱을 빌드하는 과정에서 기기의 카메라를 호출할 때 사용할 수 있는 미디어 파일을 위한 File 또는 Uri 위치를 생성하는 방법을 보여줍니다.

Kotlin

val MEDIA_TYPE_IMAGE = 1
val MEDIA_TYPE_VIDEO = 2

/** Create a file Uri for saving an image or video */
private fun getOutputMediaFileUri(type: Int): Uri {
    return Uri.fromFile(getOutputMediaFile(type))
}

/** Create a File for saving an image or video */
private fun getOutputMediaFile(type: Int): File? {
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    val mediaStorageDir = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
            "MyCameraApp"
    )
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    mediaStorageDir.apply {
        if (!exists()) {
            if (!mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory")
                return null
            }
        }
    }

    // Create a media file name
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    return when (type) {
        MEDIA_TYPE_IMAGE -> {
            File("${mediaStorageDir.path}${File.separator}IMG_$timeStamp.jpg")
        }
        MEDIA_TYPE_VIDEO -> {
            File("${mediaStorageDir.path}${File.separator}VID_$timeStamp.mp4")
        }
        else -> null
    }
}

자바

public static final int MEDIA_TYPE_IMAGE = 1;
public static final int MEDIA_TYPE_VIDEO = 2;

/** Create a file Uri for saving an image or video */
private static Uri getOutputMediaFileUri(int type){
      return Uri.fromFile(getOutputMediaFile(type));
}

/** Create a File for saving an image or video */
private static File getOutputMediaFile(int type){
    // To be safe, you should check that the SDCard is mounted
    // using Environment.getExternalStorageState() before doing this.

    File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
              Environment.DIRECTORY_PICTURES), "MyCameraApp");
    // This location works best if you want the created images to be shared
    // between applications and persist after your app has been uninstalled.

    // Create the storage directory if it does not exist
    if (! mediaStorageDir.exists()){
        if (! mediaStorageDir.mkdirs()){
            Log.d("MyCameraApp", "failed to create directory");
            return null;
        }
    }

    // Create a media file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File mediaFile;
    if (type == MEDIA_TYPE_IMAGE){
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "IMG_"+ timeStamp + ".jpg");
    } else if(type == MEDIA_TYPE_VIDEO) {
        mediaFile = new File(mediaStorageDir.getPath() + File.separator +
        "VID_"+ timeStamp + ".mp4");
    } else {
        return null;
    }

    return mediaFile;
}

참고: Environment.getExternalStoragePublicDirectory()는 Android 2.2(API 수준 8) 이상에서 이용할 수 있습니다. 이전 버전의 Android를 사용하는 기기를 대상으로 하는 경우, 대신 Environment.getExternalStorageDirectory()를 사용하세요. 자세한 내용은 공유된 파일 저장을 참조하세요.

URI가 직장 프로필을 지원하게 하려면 우선 파일 URI를 콘텐츠 URI로 변환합니다. 그런 다음, 콘텐츠 URI를 IntentEXTRA_OUTPUT에 추가합니다.

Android 기기에 파일을 저장하는 방법에 관한 자세한 내용은 데이터 저장소를 참조하세요.

카메라 기능

Android는 카메라 애플리케이션으로 제어할 수 있는 다양한 카메라 기능(예: 사진 형식, 플래시 모드, 초점 설정 등)을 지원합니다. 이 섹션에서는 보편적인 카메라 기능을 소개하고 사용 방법에 대해 간략히 설명합니다. 대부분의 카메라 기능은 Camera.Parameters 객체를 통해 액세스하고 설정할 수 있습니다. 그러나 Camera.Parameters에서 단순한 설정 이상의 것을 요구하는 중요한 기능도 몇 가지 있습니다. 이러한 기능은 다음 섹션에서 다루었습니다.

Camera.Parameters를 통해 제어되는 기능의 사용 방법에 대한 자세한 내용은 카메라 기능 사용 섹션을 검토하세요. 카메라 매개변수 객체를 통해 제어되는 기능을 사용하는 방법에 관한 자세한 내용은 아래 기능 목록에 있는 링크를 따라 API 참조 문서를 확인하세요.

표 1. 보편적인 카메라 기능(각 기능이 도입된 Android API 수준을 기준으로 분류).

기능 API 수준 설명
얼굴 인식 14 사진에서 사람의 얼굴을 식별하여 초점, 측정 및 화이트밸런스 조정에 사용
측정 영역 14 이미지에서 화이트밸런스를 계산할 영역을 하나 이상 지정
초점 영역 14 이미지 내에서 초점을 맞추는 데 사용할 영역을 하나 이상 설정
White Balance Lock 14 자동 화이트밸런스 조정 중지 또는 시작
Exposure Lock 14 자동 노출 조정 중지 또는 시작
Video Snapshot 14 동영상을 촬영하는 동시에 사진 촬영(프레임 그랩)
타임랩스 동영상 11 지연이 설정된 프레임을 녹화하여 타임랩스 동영상 기록
Multiple Cameras 9 한 기기에서 두 대 이상의 카메라 지원, 전면 및 후면 카메라 포함
Focus Distance 9 카메라와 초점 내에 있는 물체 사이의 거리 보고
Zoom 8 이미지 배율 설정
Exposure Compensation 8 빛 노출 수준 증가 또는 감소
GPS Data 5 이미지에 지리적 위치 데이터 포함 또는 생략
White Balance 5 화이트밸런스 모드 설정(캡처된 이미지의 색상 값에 영향을 미침)
Focus Mode 5 카메라가 피사체에 초점을 맞추는 방식 설정(예: 자동, 고정, 매크로 또는 무한대)
Scene Mode 5 특정 사진 촬영 상황에 대한 사전 설정 모드 적용(예: 야간, 해변, 촛불을 켠 장면)
JPEG Quality 5 JPEG 이미지의 압축 수준 설정(이미지 출력 파일의 화질 및 크기 증가 또는 감소)
Flash Mode 5 플래시 켜기, 끄기 또는 자동 설정 사용
Color Effects 5 캡처된 이미지에 색상 효과 적용(예: 흑백, 세피아 톤 또는 네거티브)
Anti-Banding 5 JPEG 압축에 의한 색 그라데이션의 줄무늬 효과 완화
Picture Format 1 사진의 파일 형식 지정
Picture Size 1 저장된 사진의 픽셀 크기 지정

참고: 이러한 기능은 하드웨어 차이와 구현된 소프트웨어로 인해 지원되지 않는 기기도 있습니다. 애플리케이션이 실행되는 기기에서 기능을 이용할 수 있는지 확인하기 위한 정보는 기능 가용성 확인을 참조하세요.

기능 가용성 확인

Android 기기에서 카메라 기능을 사용하기 위해 설정할 때 가장 먼저 알아두어야 할 점은, 카메라 기능 중에는 기기에 따라 지원되지 않는 것도 있다는 사실입니다. 또한 특정 기능을 지원하는 기기라고 하더라도 지원하는 수준이나 옵션이 다를 수 있습니다. 따라서 카메라 애플리케이션을 개발하는 과정에서 결정해야 할 사항 중에는 지원할 카메라 기능이 무엇이고 어느 수준까지 지원할지 등이 포함됩니다. 이런 결정을 내린 뒤에는 기기 하드웨어가 특정 기능을 지원하는지 확인한 다음 그런 기능을 이용할 수 없으면 실행에 실패하도록 만드는 코드를 카메라 애플리케이션에 포함하도록 계획을 세워야 합니다.

카메라 기능의 이용 가능성을 확인하려면 카메라의 매개변수 객체 인스턴스를 가져와 관련 메서드를 확인하면 됩니다. 다음 코드 샘플은 Camera.Parameters 객체를 획득하여 카메라가 자동 초점 기능을 지원하는지 확인하는 방법을 보여줍니다.

Kotlin

val params: Camera.Parameters? = camera?.parameters
val focusModes: List<String>? = params?.supportedFocusModes
if (focusModes?.contains(Camera.Parameters.FOCUS_MODE_AUTO) == true) {
    // Autofocus mode is supported
}

자바

// get Camera parameters
Camera.Parameters params = camera.getParameters();

List<String> focusModes = params.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
  // Autofocus mode is supported
}

위에 표시된 기술은 대부분의 카메라 기능에 사용할 수 있습니다. Camera.Parameters 객체는 getSupported...(), is...Supported() 또는 getMax...() 메서드를 제공하여 주어진 기능이 지원되는지 (및 어느 정도까지 지원되는지) 판별합니다.

애플리케이션이 제대로 작동하기 위해 특정 카메라 기능이 필요한 경우, 애플리케이션 매니페스트에 요구사항으로 추가하여 필수로 설정할 수 있습니다. 특정 카메라 기능(예: 플래시, 자동 초점) 사용을 선언하면 Google Play에서 애플리케이션이 그러한 기능을 지원하지 않는 기기에 설치되지 않도록 제한합니다. 앱 매니페스트에서 선언할 수 있는 카메라 기능의 목록은 기능 참조 매니페스트를 확인하세요.

카메라 기능 사용

대부분의 카메라 기능은 Camera.Parameters 객체를 사용하여 활성화 및 제어됩니다. 이 객체는 먼저 Camera 객체의 인스턴스를 가져온 다음 getParameters() 메서드를 호출하고 반환된 매개변수 객체를 변경한 후 이를 다시 카메라 객체에 설정하면 획득할 수 있습니다. 다음의 예시 코드를 참조하세요.

Kotlin

val params: Camera.Parameters? = camera?.parameters
params?.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
camera?.parameters = params

자바

// get Camera parameters
Camera.Parameters params = camera.getParameters();
// set the focus mode
params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
// set Camera parameters
camera.setParameters(params);

이 기술은 거의 모든 카메라 기능에 사용 가능하며, 대부분의 매개변수는 Camera 객체 인스턴스를 획득한 후 언제든 변경할 수 있습니다. 일반적으로 매개변수 변경사항은 애플리케이션 카메라 미리보기에서 사용자에게 즉시 표시됩니다. 소프트웨어 측면에서는 매개변수 변경이 실제로 적용되기까지 몇 프레임 정도가 걸릴 수 있는데, 이는 카메라 하드웨어가 새 지시를 처리한 다음에 업데이트된 이미지 데이터를 보내기 때문입니다.

중요: 일부 카메라 기능은 원하는 대로 변경할 수 없습니다. 특히 카메라 미리보기의 크기 또는 방향을 변경하려면 우선 미리보기를 중지하고, 미리보기 크기를 변경한 다음 미리보기를 다시 시작해야 합니다. Android 4.0(API 수준 14)부터는 미리보기를 다시 시작하지 않고도 미리보기 방향을 변경할 수 있습니다.

다른 카메라 기능의 경우 구현하기 위해 더 많은 코드가 필요합니다.

  • 측정 및 초점 영역
  • 얼굴 인식
  • 타임랩스 동영상

이 기능을 구현하는 방법에 대한 내용은 다음 섹션에서 간략히 설명합니다.

측정 및 초점 영역

사진 촬영과 관련된 시나리오에서 자동 초점과 광량 측정을 실시해도 원하는 결과가 나오지 않을 수 있습니다. Android 4.0(API 수준 14)부터는 카메라 애플리케이션이 추가적인 컨트롤을 제공하여 앱 또는 사용자가 이미지에서 영역을 지정해 초점이나 광량 설정을 결정하는 데 사용하고, 이러한 값을 카메라 하드웨어에 전달하여 이미지나 동영상을 캡처할 때 사용하도록 할 수 있습니다.

측정 및 초점 영역은 다른 카메라 기능과 매우 유사하게 작동하는데, Camera.Parameters 객체에서 메서드를 통해 제어한다는 점이 유사합니다. 다음 코드는 Camera의 인스턴스에 대해 광량 측정 영역 2개를 설정하는 방법을 보여줍니다.

Kotlin

// Create an instance of Camera
camera = getCameraInstance()

// set Camera parameters
val params: Camera.Parameters? = camera?.parameters

params?.apply {
    if (maxNumMeteringAreas > 0) { // check that metering areas are supported
        meteringAreas = ArrayList<Camera.Area>().apply {
            val areaRect1 = Rect(-100, -100, 100, 100) // specify an area in center of image
            add(Camera.Area(areaRect1, 600)) // set weight to 60%
            val areaRect2 = Rect(800, -1000, 1000, -800) // specify an area in upper right of image
            add(Camera.Area(areaRect2, 400)) // set weight to 40%
        }
    }
    camera?.parameters = this
}

자바

// Create an instance of Camera
camera = getCameraInstance();

// set Camera parameters
Camera.Parameters params = camera.getParameters();

if (params.getMaxNumMeteringAreas() > 0){ // check that metering areas are supported
    List<Camera.Area> meteringAreas = new ArrayList<Camera.Area>();

    Rect areaRect1 = new Rect(-100, -100, 100, 100);    // specify an area in center of image
    meteringAreas.add(new Camera.Area(areaRect1, 600)); // set weight to 60%
    Rect areaRect2 = new Rect(800, -1000, 1000, -800);  // specify an area in upper right of image
    meteringAreas.add(new Camera.Area(areaRect2, 400)); // set weight to 40%
    params.setMeteringAreas(meteringAreas);
}

camera.setParameters(params);

Camera.Area 객체에는 두 개의 데이터 매개변수가 포함되어 있습니다. 하나는 카메라의 시야와 가중치 값 내에서 영역을 지정하기 위한 Rect 객체로, 카메라에 광량 측정이나 초점 계산 시 이 영역의 중요도를 알려줍니다.

다른 하나는 Camera.Area 객체 내의 Rect 필드로, 2000 x 2000 단위 그리드에서 매핑된 직사각형 형태를 설명합니다. -1000, -1000 좌표는 카메라 이미지의 상단, 왼쪽 모서리를 나타내고 1000, 1000 좌표는 카메라 이미지의 하단, 오른쪽 모서리를 나타냅니다. 아래의 그림을 참조하세요.

그림 1. 빨간색 선은 카메라 미리보기 내에서 Camera.Area를 지정하기 위한 좌표계를 나타냅니다. 파란색 상자는 Rect 값 333,333,667,667로 카메라 영역의 위치와 형태를 보여줍니다.

이 좌표계의 경계는 항상 카메라 미리보기에 표시되는 이미지의 바깥쪽 가장자리와 상응하며, 확대/축소 수준에 따라 줄어들거나 확장되지 않습니다. 마찬가지로, Camera.setDisplayOrientation()을 사용하는 이미지 미리보기를 회전해도 좌표계가 다시 매핑되지는 않습니다.

얼굴 인식

인물이 포함된 사진의 경우, 사진에서 가장 중요한 요소는 얼굴인 경우가 많고 이미지를 캡처할 때 초점과 화이트밸런스를 결정하는 데도 인물의 얼굴을 사용해야 합니다. Android 4.0(API 수준 14) 프레임워크는 얼굴 인식 기술을 사용하여 얼굴을 식별하고 사진 설정을 계산하는 API를 제공합니다.

참고: 얼굴 인식 기능이 실행되는 동안 setWhiteBalance(String), setFocusAreas(List<Camera.Area>)setMeteringAreas(List<Camera.Area>)에는 영향을 미치지 않습니다.

카메라 애플리케이션에서 얼굴 인식 기능을 사용하려면 몇 가지 일반적인 단계가 필요합니다.

  • 기기에서 얼굴 인식이 지원되는지 확인
  • 얼굴 인식 리스너 만들기
  • 카메라 객체에 얼굴 인식 리스너 추가
  • 미리보기 이후(및 미리보기 다시 시작 후 매번) 얼굴 인식 시작

일부 기기는 얼굴 인식 기능을 지원하지 않습니다. getMaxNumDetectedFaces()를 호출하면 이 기능이 지원되는지 확인할 수 있습니다. 이 검사의 예는 아래의 startFaceDetection() 샘플 메서드에서 확인할 수 있습니다.

얼굴 인식에 대해 알림을 받고 응답하려면 카메라 애플리케이션에 얼굴 인식 이벤트에 대한 리스너를 설정해야 합니다. 이렇게 하려면 아래 예시 코드와 같이 Camera.FaceDetectionListener 인터페이스를 구현하는 리스너 클래스를 만들어야 합니다.

Kotlin

internal class MyFaceDetectionListener : Camera.FaceDetectionListener {

    override fun onFaceDetection(faces: Array<Camera.Face>, camera: Camera) {
        if (faces.isNotEmpty()) {
            Log.d("FaceDetection", ("face detected: ${faces.size}" +
                    " Face 1 Location X: ${faces[0].rect.centerX()}" +
                    "Y: ${faces[0].rect.centerY()}"))
        }
    }
}

자바

class MyFaceDetectionListener implements Camera.FaceDetectionListener {

    @Override
    public void onFaceDetection(Face[] faces, Camera camera) {
        if (faces.length > 0){
            Log.d("FaceDetection", "face detected: "+ faces.length +
                    " Face 1 Location X: " + faces[0].rect.centerX() +
                    "Y: " + faces[0].rect.centerY() );
        }
    }
}

이 클래스를 만들었으면 이를 애플리케이션의 Camera 객체에 설정합니다. 아래의 예시 코드를 참조하세요.

Kotlin

camera?.setFaceDetectionListener(MyFaceDetectionListener())

자바

camera.setFaceDetectionListener(new MyFaceDetectionListener());

애플리케이션은 카메라 미리보기를 시작(또는 다시 시작)할 때마다 얼굴 인식 기능을 시작해야 합니다. 필요할 때마다 호출할 수 있도록 얼굴 인식을 시작하는 메서드를 만들어야 합니다. 아래의 예시 코드를 참조하세요.

Kotlin

fun startFaceDetection() {
    // Try starting Face Detection
    val params = mCamera?.parameters
    // start face detection only *after* preview has started

    params?.apply {
        if (maxNumDetectedFaces > 0) {
            // camera supports face detection, so can start it:
            mCamera?.startFaceDetection()
        }
    }
}

자바

public void startFaceDetection(){
    // Try starting Face Detection
    Camera.Parameters params = mCamera.getParameters();

    // start face detection only *after* preview has started
    if (params.getMaxNumDetectedFaces() > 0){
        // camera supports face detection, so can start it:
        mCamera.startFaceDetection();
    }
}

카메라 미리보기를 시작(또는 다시 시작)할 때마다 매번 얼굴 인식을 시작해야 합니다. 미리보기 클래스 만들기에 나온 미리보기 클래스를 사용하려면 startFaceDetection() 메서드를 미리보기 클래스에 있는 surfaceCreated()surfaceChanged() 메서드에 모두 추가해야 합니다. 아래의 예시 코드를 참조하세요.

Kotlin

override fun surfaceCreated(holder: SurfaceHolder) {
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // start face detection feature
    } catch (e: IOException) {
        Log.d(TAG, "Error setting camera preview: ${e.message}")
    }
}

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    if (holder.surface == null) {
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null")
        return
    }
    try {
        mCamera.stopPreview()
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: ${e.message}")
    }
    try {
        mCamera.setPreviewDisplay(holder)
        mCamera.startPreview()

        startFaceDetection() // re-start face detection feature
    } catch (e: Exception) {
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: ${e.message}")
    }
}

자바

public void surfaceCreated(SurfaceHolder holder) {
    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // start face detection feature

    } catch (IOException e) {
        Log.d(TAG, "Error setting camera preview: " + e.getMessage());
    }
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {

    if (holder.getSurface() == null){
        // preview surface does not exist
        Log.d(TAG, "holder.getSurface() == null");
        return;
    }

    try {
        mCamera.stopPreview();

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error stopping camera preview: " + e.getMessage());
    }

    try {
        mCamera.setPreviewDisplay(holder);
        mCamera.startPreview();

        startFaceDetection(); // re-start face detection feature

    } catch (Exception e){
        // ignore: tried to stop a non-existent preview
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}

참고: 이 메서드는 startPreview()를 호출한 이후에 호출해야 한다는 것을 명심하세요. 카메라 앱 메인 Activity의 onCreate() 메서드에서 얼굴 인식을 시작하려고 하지 마세요. 애플리케이션 실행 중 이 시점에서는 미리보기를 이용할 수 없습니다.

타임랩스 동영상

타임랩스 동영상을 사용하면 사용자는 몇 초 또는 몇 분 간격을 두고 촬영한 사진을 결합하여 동영상 클립을 만들 수 있습니다. 이 기능은 MediaRecorder를 사용하여 타임랩스 시퀀스의 이미지를 기록합니다.

MediaRecorder로 타임랩스 동영상을 기록하려면 일반적인 동영상을 기록하는 것과 마찬가지로 녹음기 객체를 구성해야 합니다. 초당 캡처된 프레임을 낮은 숫자로 설정하고, 타임랩스 품질 설정 중 하나를 설정합니다. 아래의 코드 예시를 참조하세요.

Kotlin

mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH))
mediaRecorder.setCaptureRate(0.1) // capture a frame every 10 seconds

자바

// Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
mediaRecorder.setProfile(CamcorderProfile.get(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH));
...
// Step 5.5: Set the video capture rate to a low number
mediaRecorder.setCaptureRate(0.1); // capture a frame every 10 seconds

이러한 설정은 MediaRecorder에 대한 대규모 구성 절차의 일부로 완료해야 합니다. 전체 구성 코드 예시는 MediaRecorder 구성을 참조하세요. 구성이 완료되면 일반적인 동영상을 녹화하는 것처럼 동영상 녹화를 시작합니다. MediaRecorder를 구성하고 실행하는 방법에 관한 자세한 내용은 동영상 캡처를 참조하세요.

Camera2VideoHdrViewfinder 샘플은 이 페이지에서 다룬 API 사용을 더 자세히 보여줍니다.

권한이 필요한 카메라 필드

Android 10(API 수준 29) 이상을 실행하는 앱은 getCameraCharacteristics() 메서드가 반환하는 다음 필드의 값에 액세스하려면 CAMERA 권한이 있어야 합니다.

  • LENS_POSE_ROTATION
  • LENS_POSE_TRANSLATION
  • LENS_INTRINSIC_CALIBRATION
  • LENS_RADIAL_DISTORTION
  • LENS_POSE_REFERENCE
  • LENS_DISTORTION
  • LENS_INFO_HYPERFOCAL_DISTANCE
  • LENS_INFO_MINIMUM_FOCUS_DISTANCE
  • SENSOR_REFERENCE_ILLUMINANT1
  • SENSOR_REFERENCE_ILLUMINANT2
  • SENSOR_CALIBRATION_TRANSFORM1
  • SENSOR_CALIBRATION_TRANSFORM2
  • SENSOR_COLOR_TRANSFORM1
  • SENSOR_COLOR_TRANSFORM2
  • SENSOR_FORWARD_MATRIX1
  • SENSOR_FORWARD_MATRIX2

추가 샘플 코드

샘플 앱을 다운로드하려면 Camera2Basic 샘플공식 CameraX 샘플 앱을 참조하세요.