Controlar a câmera

Nesta lição, vamos discutir como controlar o hardware da câmera usando diretamente as APIs do framework.

Observação: esta página se refere à classe Camera, que foi descontinuada. Recomendamos usar CameraX ou, para casos de uso específicos, Camera2. CameraX e Camera2 oferecem suporte ao Android 5.0 (nível 21 da API) e versões mais recentes.

O controle direto da câmera de um dispositivo requer um código muito maior do que o necessário para solicitar imagens ou vídeos de aplicativos de câmera. Se você quer criar um aplicativo de câmera especializado ou algo totalmente integrado à IU do seu app, este guia mostra como fazer isso.

Confira os recursos relacionados abaixo:

Abrir o objeto "Camera"

Acessar uma instância do objeto Camera é a primeira etapa do processo de controlar a câmera diretamente. A maneira recomendada de acessar a câmera é abrir Camera em uma linha de execução separada, iniciada pelo método onCreate(), como é feito pelo aplicativo Câmera do Android. Essa é uma boa abordagem, já que o acesso à câmera pode demorar um pouco e pode sobrecarregar a linha de execução de interface. Em uma implementação mais básica, o processo de abrir a câmera pode ser transferido para o método onResume() para facilitar a reutilização do código e manter o fluxo de controle simples.

Chamar Camera.open() vai gerar uma exceção se a câmera já estiver sendo usada por outro app. Por isso, nós a colocamos em um bloco 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;
    }
}

No nível 9 da API e mais recentes, o framework da câmera oferece suporte a várias câmeras. Se você usar a API legada e chamar open() sem um argumento, vai utilizar a primeira câmera traseira.

Criar a visualização da câmera

Para tirar uma foto, geralmente é necessário que os usuários visualizem a imagem desejada antes de clicar no obturador. Para fazer isso, você pode usar uma SurfaceView para mostrar visualizações do que o sensor da câmera está captando.

Classe de visualização

Para mostrar uma visualização, você precisa da classe dela. A visualização requer uma implementação da interface android.view.SurfaceHolder.Callback, que é usada para transmitir dados de imagem do hardware da câmera ao app.

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);
    }
...
}

A classe de visualização precisa ser transmitida para o objeto Camera antes que a visualização da imagem ao vivo possa ser iniciada, conforme mostrado na próxima seção.

Configurar e iniciar a visualização

Uma instância da câmera e a visualização relacionada precisam ser criadas em uma ordem específica, com o objeto Camera em primeiro lugar. No snippet abaixo, o processo de inicialização da câmera é encapsulado para que Camera.startPreview() seja chamado pelo método setCamera() sempre que o usuário fizer alguma mudança na câmera. A visualização também precisa ser reiniciada no método de callback surfaceChanged() da classe de visualização.

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();
    }
}

Modificar configurações da câmera

As configurações da câmera mudam a forma como a câmera tira fotos, do nível de zoom à compensação de exposição. Este exemplo muda somente o tamanho da visualização. Consulte o código-fonte do app Câmera para outros detalhes.

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();
}

Configurar a orientação da visualização

A maioria dos aplicativos de câmera bloqueiam a tela no modo paisagem porque essa é a orientação natural do sensor da câmera. Essa configuração não impede que você tire fotos no modo retrato, porque a orientação do dispositivo é gravada no cabeçalho EXIF. O método setCameraDisplayOrientation() permite que você mude a forma como a visualização é mostrada, sem afetar como a imagem é gravada. No entanto, em versões do Android anteriores ao nível 14 da API, é necessário interromper a visualização antes de mudar a orientação e a reiniciar.

Tirar uma foto

Use o método Camera.takePicture() para tirar uma foto quando a visualização for iniciada. Você pode criar objetos de Camera.PictureCallback e Camera.ShutterCallback e os transmitir para Camera.takePicture().

Se você quer capturar imagens de forma contínua, crie um Camera.PreviewCallback que implemente onPreviewFrame(). Para um resultado intermediário, você pode capturar somente frames de visualização selecionados ou configurar uma ação atrasada para chamar takePicture().

Reiniciar a visualização

Depois que uma foto é tirada, é necessário reiniciar a visualização para que o usuário possa tirar outra foto. Neste exemplo, a reinicialização é feita sobrecarregando o botão do obturador.

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();
}

Parar a visualização e liberar a câmera

Depois que o aplicativo termina de usar a câmera, é hora de limpar os dados. Você precisa liberar principalmente o objeto Camera para não correr o risco de travar outros aplicativos, incluindo novas instâncias do seu app.

Quando é preciso interromper a visualização e liberar a câmera? Ter a superfície de visualização destruída é uma boa dica de que é hora de interromper a visualização e liberar a câmera, conforme mostrado nestes métodos da classe 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;
    }
}

Anteriormente no guia, esse procedimento também fazia parte do método setCamera(). Portanto, para inicializar uma câmera, sempre é necessário interromper a visualização primeiro.