Carlos Eduardo Gusso Tosin é formado em Ciência da Computação pela PUC-PR, pós-graduado em Desenvolvimento de Jogos para Computador pela Universidade
Positivo, Mestre em Informática na área de Sistemas Distribuídos, também pela PUC-PR e instrutor oficial dos cursos de Java da Softblue. Trabalha profissionalmente com Java desde 2002 e possui 5
anos de experiência no desenvolvimento de sistemas para a IBM dos Estados Unidos, utilizados a nível mundial. Atua há mais de 3 anos com cursos
e treinamentos de profissionais em grandes empresas e escreve artigos para a revista Java Magazine. Possui as certificações da Sun
SCJP,
SCJD,
SCWCD,
SCBCD,
SCEA,
IBM SOA, IBM OOAD (Object Oriented Analysis and Design) e
ITIL Foundation.
Utilizando Câmeras em Aplicações Android
Publicado em 13/03/2012 às 13:50:41 horas.
Introdução
Grande parte dos dispositivos Android possui uma ou mais câmeras, que permitem tirar fotos e gravar vídeos. O objetivo deste artigo é mostrar como aplicações Android podem tirar proveito da presença destas câmeras.
A integração de uma aplicação com a câmera de um dispositivo pode ser feita de duas formas. A primeira, mais simples, utiliza a aplicação de câmera nativa que já existe no Android. E a segunda permite que o desenvolvedor tenha controle total do hardware da câmera. As duas formas serão abordadas na sequência.
Este artigo tem foco na utilização da câmera apenas, portanto é esperado que o leitor tenha conhecimento prévio a respeito de conceitos fundamentais do Android, como intents, activities, views e permissões de acesso.
Acessando a aplicação nativa de câmera
A forma mais simples de integrar uma aplicação com a câmera é chamar a aplicação nativa de câmera do dispositivo. A chamada de outras aplicações no Android é feita através do disparo de uma intent para a plataforma, que fica responsável por carregar a aplicação. O código que solicita a abertura da aplicação nativa da câmera pode ser visto a seguir:
File picsDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File imageFile = new File(picsDir, "foto.jpg");
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
startActivity(i);
A intent deve ser criada com a action MediaStore.ACTION_IMAGE_CAPTURE, que identifica a aplicação nativa de câmera. Dentro de intent, é fornecido também o caminho para o arquivo onde a foto tirada será armazenada. Neste caso, o armazenamento está sendo feito na memória externa do dispositivo, dentro da pasta onde normalmente as fotos ficam guardadas (mas poderia ser em outro local). Lembrando que, se você deseja gravar dados na área de memória externa, é preciso que sua aplicação declare a permissão android.permission.WRITE_EXTERNAL_STORAGE no arquivo AndroidManifest.xml.
Depois de criada a intent, o método startActivity() é chamado. É ele quem envia a intent para a plataforma, a qual vai abrir a aplicação da câmera. A foto tirada por esta aplicação será armazenada no caminho especificado pela propriedade MediaStore.EXTRA_OUTPUT adicionada à intent.
Como você pode perceber, este primeiro tipo de integração é bastante simples. É preciso apenas chamar a aplicação nativa de câmera e indicar onde a foto será armazenada. Se você precisa de um controle maior ou deseja realizar uma programação mais avançada nesta área, será necessário controlar diretamente a câmera via programação. Este é o assunto que será abordado a partir de agora.
Controlando a câmera via programação
Existem situações onde o programador não quer utilizar a aplicação nativa da câmera ou quer ter um controle maior sofre ela. Nesses casos o trabalho de programação é um pouco maior, pois é preciso seguir alguns passos.
Escolhendo a câmera
Um dispositivo pode ter várias câmeras, uma câmera ou nenhuma. Para identificar o número exato é possível fazer a seguinte chamada (disponível a partir do Android 2.3):
int qtde = Camera.getNumberOfCameras();
O primeiro passo para trabalhar com uma câmera é obter uma instância de um objeto Camera (classe presente no pacote android.hardware.Camera). Isto pode ser feito desta forma:
Camera camera = Camera.open();
Esta chamada retorna a referência à câmera traseira do dispositivo (retorna null caso ela não exista). Para referenciar outras câmeras, é preciso utilizar o método open() sobrecarregado, que recebe um int como parâmetro. Este parâmetro indica qual o ID da câmera, que pode variar de 0 até o número de câmeras subtraído de 1.
Um detalhe importante com relação à câmera é que ela deve ser liberada após o uso. Isto é feito através da seguinte chamada:
camera.release();
Além disso, quando você acessa diretamente a câmera, o Android solicita que você declare uma permissão específica na sua aplicação, como pode ser visto abaixo:
Para que você possa tirar uma foto é necessário que você esteja vendo um preview da imagem, isto é, esteja vendo o que a câmera está enquadrando. Para trabalhar com preview, a classe SurfaceView é utilizada. Esta classe representa uma view, o que permite a sua utilização em um arquivo XML de definição de layout. Veja um exemplo:
public class CameraActivity extends Activity
implements SurfaceHolder.Callback {
private Camera camera;
private SurfaceView surfaceView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
camera = Camera.open();
surfaceView = (SurfaceView) findViewById(R.id.preview);
surfaceView.getHolder().addCallback(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (camera != null) {
camera.release();
}
}
@Override
protected void onPause() {
super.onPause();
if (camera != null) {
camera.stopPreview();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
if (holder.getSurface() != null) {
try {
camera.stopPreview();
} catch (Exception e) {
}
try {
camera.setPreviewDisplay(holder);
camera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
Para trabalhar com um SurfaceView, é preciso registrar um callback. Isto é feito através do método surfaceView.getHolder().addCallback(). Este callback é um objeto de uma classe que implementa SurfaceHolder.Callback (neste caso, a própria activity).
A interface SurfaceHolder.Callback declara três métodos:
- surfaceCreated(): Chamado quando a superfície, que é gerenciada pelo SurfaceView, é criada. O preview da imagem da câmera só pode ser iniciado quando a superfície é criada. - surfaceChanged(): Chamado quando a superfície é tem suas características alteradas. Se um preview está em andamento quando este método é chamado, ele deve ser reinicializado. - surfaceDestroyed(): Chamado quando a superfície é destruída.
O preview é iniciado ou parado, respectivamente, através dos métodos camera.startPreview() e camera.stopPreview(). O método camera.setPreviewDisplay() atrela a SurfaceView (onde o preview será mostrado) à câmera (que gera as imagens que serão exibidas).
Tirando fotos
Depois que a câmera foi inicializada e o preview está sendo exibido, é possível tirar fotos. Isto é feito através da seguinte chamada:
camera.takePicture(shutter, raw, jpeg);
O método takePicture() é assíncrono. Conforme a imagem vai sendo processada, o Android invoca os callbacks passados como parâmetro. Os callbacks que não interessam para a aplicação não precisam ser fornecidos (neste caso null deve ser passado como parâmetro). O parâmetros deste método são os seguintes:
- shutter: Objeto cuja classe implementa a interface ShutterCallback. Invocado assim que o obturador da câmera entra em ação e captura a imagem. - raw: Objeto cuja classe implementa a interface PictureCallback. Invocado quando a imagem “crua” tirada pela câmera está disponível. - jpeg: Objeto cuja classe implementa a interface PictureCallback. Invocado assim que a imagem comprimida, no formato JPEG, está disponível.
Veja um exemplo de implementação de um callback que grava a imagem no formato JPEG em algum local do dispositivo, assim que a imagem esteja disponível:
PictureCallback jpeg = new PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
FileOutputStream fos = null;
try {
try {
fos = new FileOutputStream(imageFile);
fos.write(data);
} finally {
if (fos != null) {
fos.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
camera.startPreview();
}
O método onPictureTaken() recebe como parâmetro um array de bytes, que representa o conteúdo da imagem. Este array deve ser usado para gerar o arquivo de imagem, por exemplo.
Outro detalhe importante é a chamada do método camera.startPreview() depois que a foto é gerada. Isto é necessário caso você deseje continuar exibindo o preview para tirar outras fotos.
Para tirar a foto utilizando o callback criado, a chamada pode ser feita assim:
camera.takePicture(null, null, jpeg);
Seguindo estes passos você conseguirá ter controle total sobre a câmera e poderá utilizá-la de acordo com a sua necessidade.
Conclusão
Este artigo apresentou as duas formas de integrar aplicações Android com as câmeras presentes no dispositivo. A primeira através do uso da própria aplicação de câmera nativa do Android, e a segunda através do controle direto da câmera pela aplicação.
Muito obrigado Carlos ! Assim que possível irei tentar isolar o problema para detectar o que está ocorrendo.
Mais uma vez obrigado pela resposta !
Enviado em 04/05/2012 às 13:26:55 horas, por Leandro
Leandro, não acredito que haja problemas em usar um AsyncTask no seu caso. Se você quiser mudar o mecanismo para fazer alguns testes, você pode substituí-lo pela criação de uma thread normal mesmo. Para efeitos de teste, você pode tentar tirar a parte multithread do seu código, e fazer tudo dentro do onPictureTaken() para ver se resolve. Acho que a melhor alternativa é tentar isolar o problema e usar técnicas de debug. Abraço!
Enviado em 04/05/2012 às 09:45:40 horas, por Carlos Tosin
Obrigado pela resposta Carlos.
Ainda nao descobri o problema. Você mencionou que poderia ser algo relacionado à thread. Eu realizo algumas operações em AsyncTask no método "onPictureTaken". Este processo é necessário para salvar a foto tirada em um determinado local e logo em seguida convertê-la para hexadecimal. O uso desta asynctask pode causar este tipo de problema ? se sim, como faço para nao utilizá-lo sendo que o processo leva um tempo e seria interessante informar ao usuário que o mesmo está sendo executado com um dialog ?
Enviado em 03/05/2012 às 08:11:48 horas, por Leandro
Leandro, acho difícil dizer para você o que é o problema. Nunca vi algo parecido. Isso pode ser algum problema no seu código ou até algum bug no dispositivo que você está usando para executar a sua aplicação (às vezes isso acontece). Acho que a sua melhor chance é colocar umas mensagens de log e, quando o problema ocorrer, você dar uma olhada nas mensagens. Às vezes pode ser algum problema com o sincronismo das threads, por exemplo. De qualquer forma, boa sorte!
Enviado em 27/04/2012 às 20:58:44 horas, por Carlos Tosin
Saudações, Primeiramente gostaria de parabenizar pelo artigo, muito bem explicado, completo e tirou algumas dúvidas que tinha.
Porém estou com um problema, na minha aplicação existe um formulario a ser preenchido, cada um deles possui por exemplo 3 botões para executar a ação de startar a camera para tirar foto.
Todos funcionam corretamente como planejado, porém nao consegui realizar com sucesso o seguinte teste: "Usuario executar rapidamente a chamada para a camera alternando entre os botoes (pressionar rapidamente cada um dos botoes alternadamente)"
Em determinado momento a aplicação exibe um force close. No log pude encontrar isso. java.lang.RuntimeException: startPreview failed at android.hardware.Camera.startPreview(Native method)
Minha classe possui uma flag para indicar se o preview está ativo ou nao, só starto o preview qdo a mesma está como "false" e seto para "true" logo após a chamada do stoppreview.
Dei uma pesquisada pela net e tudo que encontro é à respeito do que ja fiz ... Não sei oq mais devo procurar, poderia me ajudar ?
ps: no log completo antes da runtime existem 2 linhas em vermelho SecCamera ERR(startPreview):Camera was closed CameraHardwareSec ERR(startPreview): Fail on mSecCamera->startPreview().
A principio parace que esse log indica que a camera nao foi inicializada com sucesso, porém através de log pude comprovar que a mesma está diferente de null.
Enviado em 27/04/2012 às 08:58:31 horas, por Leandro
Não precisa mais, foi uma falha minha! Obrigada.
Enviado em 09/04/2012 às 12:28:40 horas, por Ana Ferreira
A API do Android tem duas classes Camera, uma no pacote android.graphics e outra no pacote android.hardware. Certifique-se de que você está importando a classe certa, do pacote android.hardware.
Att,
Enviado em 09/04/2012 às 11:54:34 horas, por Carlos Tosin
Olá Carlos,
Achei muito interessante o post. Já havia lido alguns sobre o tema e este foi o que me esclareceu mais.
Estou desenvolvendo uma app para a versão 2.1. Na linha "camera.takePicture(null, null, jpeg)" o eclipse aponta o erro "The method tiraFoto(null, null, Camera.PictureCallback) is undefined for the type Camera".
Você sabe como posso resolvê-lo? Obrigada.
Enviado em 09/04/2012 às 11:39:44 horas, por Ana Ferreira
Nossos cursos são criados com base na realidade do mercado de trabalho, abordando teoria e prática das tecnologias. Conheça nossos cursos clicando aqui.