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.