카메라 제어하기

이 과정에서는 프레임워크 API를 사용하여 직접 카메라 하드웨어를 제어하는 방법을 설명합니다.

참고: 이 페이지에서는 지원 중단된 Camera 클래스를 다룹니다. CameraX, 또는 특정 사용 사례의 경우 Camera2를 사용하는 것이 좋습니다. CameraX와 Camera2는 모두 Android 5.0(API 수준 21) 이상을 지원합니다.

기기의 카메라를 직접 제어하려면 기존 카메라 애플리케이션에 사진이나 동영상을 요청하는 것보다 더 많은 코드가 필요합니다. 그러나 전문 카메라 애플리케이션 또는 앱 UI에 완전히 통합된 기능을 빌드하려는 경우 이 과정에서 그 방법을 보여줍니다.

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

카메라 객체 열기

Camera 객체에서 인스턴스를 가져오는 것이 카메라를 직접 제어하는 절차의 첫 번째 단계입니다. Android의 자체 카메라 애플리케이션이 하는 것처럼 onCreate()에서 시작된 별도의 스레드에 Camera를 열어 카메라에 접근하는 방법을 권장합니다. 이 접근 방식은 시간이 걸릴 수 있고 UI 스레드를 멈출 수도 있기 때문에 좋습니다. 더 기본적인 구현에서 카메라를 여는 것은 onResume() 메서드로 연기되어 코드 재사용을 용이하게 하고 제어 흐름을 간단하게 유지할 수 있습니다.

Camera.open()을 호출할 때 이미 다른 애플리케이션에서 카메라를 사용 중이라면 예외가 발생하므로 try 블록으로 호출 부분을 래핑합니다.

Kotlin

private fun safeCameraOpen(id: Int): Boolean {
    return try {
        releaseCameraAndPreview()
        mCamera = Camera.open(id)
        true
    } catch (e: Exception) {
        Log.e(getString(R.string.app_name), "failed to open Camera")
        e.printStackTrace()
        false
    }
}

private fun releaseCameraAndPreview() {
    preview?.setCamera(null)
    mCamera?.also { camera ->
        camera.release()
        mCamera = null
    }
}

Java

private boolean safeCameraOpen(int id) {
    boolean qOpened = false;

    try {
        releaseCameraAndPreview();
        camera = Camera.open(id);
        qOpened = (camera != null);
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }

    return qOpened;
}

private void releaseCameraAndPreview() {
    preview.setCamera(null);
    if (camera != null) {
        camera.release();
        camera = null;
    }
}

API 수준 9 이후로 카메라 프레임워크는 다중 카메라를 지원합니다. 기존 API를 사용하고 인수 없이 open()을 호출하면 첫 번째 후면 카메라를 가져옵니다.

카메라 미리보기 만들기

일반적으로 사진을 촬영하려면 사용자가 촬영 버튼을 클릭하기 전에 피사체를 미리 봐야 합니다. 그렇게 하려면 SurfaceView를 사용하여 카메라 센서에 포착된 피사체의 미리보기를 생성하면 됩니다.

미리보기 클래스

미리보기를 표시하려면 미리보기 클래스가 필요합니다. 미리보기에는 android.view.SurfaceHolder.Callback 인터페이스의 구현이 필요하며 이 인터페이스는 이미지 데이터를 카메라 하드웨어에서 애플리케이션으로 전달하는 데 사용됩니다.

Kotlin

class Preview(
        context: Context,
        val surfaceView: SurfaceView = SurfaceView(context)
) : ViewGroup(context), SurfaceHolder.Callback {

    var mHolder: SurfaceHolder = surfaceView.holder.apply {
        addCallback(this@Preview)
        setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
    }
    ...
}

Java

class Preview extends ViewGroup implements SurfaceHolder.Callback {

    SurfaceView surfaceView;
    SurfaceHolder holder;

    Preview(Context context) {
        super(context);

        surfaceView = new SurfaceView(context);
        addView(surfaceView);

        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        holder = surfaceView.getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }
...
}

다음 섹션에서와 같이 미리보기 클래스는 라이브 이미지 미리보기가 시작되기 전에 Camera 객체에 전달되어야 합니다.

미리보기 설정 및 시작

카메라 인스턴스 및 그와 관련된 미리보기는 특정한 순서(카메라 객체가 첫 번째)로 생성되어야 합니다. 아래 스니펫에서 카메라의 초기화 절차는 사용자가 카메라를 변경하기 위해 어떤 작업을 할 때마다 Camera.startPreview()setCamera() 메서드에 의해 호출되도록 캡슐화됩니다. 또한 미리보기는 미리보기 클래스의 surfaceChanged() 콜백 메서드에서 다시 시작되어야 합니다.

Kotlin

fun setCamera(camera: Camera?) {
    if (mCamera == camera) {
        return
    }

    stopPreviewAndFreeCamera()

    mCamera = camera

    mCamera?.apply {
        mSupportedPreviewSizes = parameters.supportedPreviewSizes
        requestLayout()

        try {
            setPreviewDisplay(holder)
        } catch (e: IOException) {
            e.printStackTrace()
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

public void setCamera(Camera camera) {
    if (mCamera == camera) { return; }

    stopPreviewAndFreeCamera();

    mCamera = camera;

    if (mCamera != null) {
        List<Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
        supportedPreviewSizes = localSizes;
        requestLayout();

        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // Important: Call startPreview() to start updating the preview
        // surface. Preview must be started before you can take a picture.
        mCamera.startPreview();
    }
}

카메라 설정 수정

카메라 설정은 확대/축소 수준, 노출 보정 등 카메라가 사진을 촬영하는 방법을 변경합니다. 이 예는 미리보기 크기만 변경합니다. 더 많은 내용은 카메라 애플리케이션의 소스 코드를 참고하세요.

Kotlin

override fun surfaceChanged(holder: SurfaceHolder, format: Int, w: Int, h: Int) {
    mCamera?.apply {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        parameters?.also { params ->
            params.setPreviewSize(previewSize.width, previewSize.height)
            requestLayout()
            parameters = params
        }

        // Important: Call startPreview() to start updating the preview surface.
        // Preview must be started before you can take a picture.
        startPreview()
    }
}

Java

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    // Now that the size is known, set up the camera parameters and begin
    // the preview.
    Camera.Parameters parameters = mCamera.getParameters();
    parameters.setPreviewSize(previewSize.width, previewSize.height);
    requestLayout();
    mCamera.setParameters(parameters);

    // Important: Call startPreview() to start updating the preview surface.
    // Preview must be started before you can take a picture.
    mCamera.startPreview();
}

미리보기 방향 설정

카메라 센서의 자연스러운 방향은 가로 모드이기 때문에 대부분의 카메라 애플리케이션은 화면을 가로 모드로 잠급니다. 기기의 방향은 EXIF 헤더에 기록되므로 이 설정 때문에 세로 모드 사진을 촬영할 수 없는 것은 아닙니다. setCameraDisplayOrientation() 메서드를 사용하면 이미지가 저장되는 방법에 영향을 주지 않고 미리보기 표시 방법을 변경할 수 있습니다. 그러나 API 수준 14 이전의 Android에서는 방향을 변경하기 전에 미리보기를 중지하고 그런 다음 미리보기를 다시 시작해야 합니다.

사진 촬영

미리보기가 시작되면 Camera.takePicture() 메서드를 사용하여 사진을 촬영하세요. Camera.PictureCallbackCamera.ShutterCallback 객체를 만들고 Camera.takePicture()로 전달할 수 있습니다.

연속해서 이미지를 가져오려면 onPreviewFrame()을 구현한 Camera.PreviewCallback을 만들면 됩니다. 연속한 이미지 사이에서 선택된 미리보기 프레임만 캡처하거나 지연된 작업을 설정하여 takePicture()를 호출할 수 있습니다.

미리보기 다시 시작

사진을 촬영한 후에는 사용자가 다른 사진을 촬영하기 전에 미리보기를 다시 시작해야 합니다. 이 예에서는 촬영 버튼을 오버로드하여 미리보기를 다시 시작합니다.

Kotlin

fun onClick(v: View) {
    previewState = if (previewState == K_STATE_FROZEN) {
        camera?.startPreview()
        K_STATE_PREVIEW
    } else {
        camera?.takePicture(null, rawCallback, null)
        K_STATE_BUSY
    }
    shutterBtnConfig()
}

Java

@Override
public void onClick(View v) {
    switch(previewState) {
    case K_STATE_FROZEN:
        camera.startPreview();
        previewState = K_STATE_PREVIEW;
        break;

    default:
        camera.takePicture( null, rawCallback, null);
        previewState = K_STATE_BUSY;
    } // switch
    shutterBtnConfig();
}

미리보기 중지 및 카메라 해제

애플리케이션에서 카메라 사용을 완료했다면 이제 정리할 시간입니다. 특히, Camera 객체를 해제해야 합니다. 그러지 않으면 다른 애플리케이션 및 자체 애플리케이션의 새 인스턴스가 비정상 종료될 수 있습니다.

언제 미리보기를 중지하고 카메라를 해제해야 할까요? Preview 클래스의 다음 메서드와 같이 미리보기 노출 영역을 없애면 미리보기를 중지하고 카메라를 해제할 때가 되었다는 것을 알릴 수 있습니다.

Kotlin

override fun surfaceDestroyed(holder: SurfaceHolder) {
    // Surface will be destroyed when we return, so stop the preview.
    // Call stopPreview() to stop updating the preview surface.
    mCamera?.stopPreview()
}

/**
 * When this function returns, mCamera will be null.
 */
private fun stopPreviewAndFreeCamera() {
    mCamera?.apply {
        // Call stopPreview() to stop updating the preview surface.
        stopPreview()

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        release()

        mCamera = null
    }
}

Java

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();
    }
}

/**
 * When this function returns, mCamera will be null.
 */
private void stopPreviewAndFreeCamera() {

    if (mCamera != null) {
        // Call stopPreview() to stop updating the preview surface.
        mCamera.stopPreview();

        // Important: Call release() to release the camera for use by other
        // applications. Applications should release the camera immediately
        // during onPause() and re-open() it during onResume()).
        mCamera.release();

        mCamera = null;
    }
}

이 과정의 앞부분에서, 이 절차는 setCamera() 메서드의 일부였으므로 카메라 초기화는 항상 미리보기 중지로 시작합니다.