Controlar a câmera

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

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, esta lição mostra como fazer isso.

Confira o seguinte recurso relacionado:

Abrir o objeto "Camera"

Conseguir 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 a partir de 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 IU. 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() gera uma exceção se a câmera já está 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;
        }
    }
    

Desde a API de nível 9, o framework da câmera é compatível com várias câmeras. Se você usar a API legada e chamar open() sem um argumento, você 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 um SurfaceView para desenhar visualizações do que o sensor da câmera está captando.

Classe de visualização

Para começar a exibir uma visualização, você precisa da classe de visualização. 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 para o 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 ver 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 é exibida, sem afetar como a imagem é gravada. No entanto, no Android anterior à API de nível 14, é necessário interromper a visualização antes de alterar a orientação e reiniciá-la.

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 passá-los 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 aplicativo.

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 na lição, esse procedimento também fazia parte do método setCamera(). Portanto, a inicialização de uma câmera sempre começa pela interrupção da visualização.