Blog da Softblue


Este artigo foi criado por Carlos Eduardo Gusso Tosin.
Conheça o currículo completo do instrutor clicando aqui.

Utilizando Câmeras em Aplicações Android

Publicado em 13/03/2012 às 13:50:41 horas.

Compartilhe:    

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:

<uses-permission android:name="android.permission.CAMERA" />

Preview da imagem

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.

Referências

Tutorial oficial do Android, ensinando a usar a câmera:
http://developer.android.com/guide/topics/media/camera.html

Código-fonte completo e comentado do artigo (projeto do Eclipse):
http://www.softblue.com.br/public/blog/15/softblue-android-camera.zip

Comentários

O comando: Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
me retorna o caminho, porém na hora de gravar o logcat me diz que o diretório ou arquivo não existe.
O estranho é que o mesmo comando é usado no exemplo do acesso à camera via intent e a foto é gravada normalmente.

Enviado em 17/01/2013 às 09:06:45 horas, por André


André, que bom que deu certo! Quanto ao diretório você deve verificar se o SD card está montado e disponível para leitura e gravação de dados. Deve estar por aí o problema. Abraço!

Enviado em 16/01/2013 às 21:14:54 horas, por Carlos Tosin


Consegui resolver o problema. Na verdade o método camera.starPreview() estava gerando uma exception. O motivo é que para versões android de 3.0 para baixo é necessário inserir o seguinte código após
surfaceView.getHolder().addCallback(this);

//necessário para versões abaixo da 3.0 surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
Feito isso funcionou, só não consigo salvar a foto ainda pelo motivo do caminho do diretório não ser válido. Se você souber o motivo fico agradecido. O comando:
File picsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
retorna o diretório, mas na hora de salvar a foto o logcat diz que o diretório não existe.
Abraço

Enviado em 16/01/2013 às 11:13:50 horas, por André


André, este código deveria funcionar na versão 2.2 sem problemas. Com relação ao onSurfaceChanged(), ele é executado só quando as características da superfície de visualização da imagem mudam. Normalmente as características não são alteradas, portanto normalmente este método é chamado só uma vez, assim que a cena começa a ser exibida. Abraço!

Enviado em 16/01/2013 às 10:03:37 horas, por Carlos Tosin


ops.. fosse capturado....

Enviado em 15/01/2013 às 16:47:43 horas, por André


Obrigado Carlos. Um teste que irei fazer será rodar a aplicação no emulador, utilizando a webcam do micro e ver se roda normal. O fato de ser a API level 8 poderia influenciar alguma coisa? O método surfaceChanged deveria ser executado toda vez que um quadro de imagem você capturado?

Enviado em 15/01/2013 às 16:17:19 horas, por André


André, infelizmente não sei o que pode ser. Existem coisas que funcionam bem em uns dispositivos e não funcionam direito em outros, então fica difícil saber o que é realmente. Minha sugestão é que você procure fóruns especializados no assunto, para tentar descobrir se é algo relacionado com o fato de você estar usando o este modelo do Galaxy. Abraço!

Enviado em 15/01/2013 às 16:10:39 horas, por Carlos Tosin


Gostei muito do tutorial, parabéns. Nos testes que fiz estou tendo problemas no acesso direto à camera (tenho um Samsung Galaxy GT-5500i com Android 2.2) , o que acontece é o seguinte:
1 - Estou testando a aplicação direto no celular
2- Percebi que o código funciona normal até o método surfaceCreated, usei uns prints no logcat para poder checar.
3- Fiz prints antes e depois de chamar o método camera.setPreviewDisplay(holder); e o mesmo não deu erro e nem exceção, porém a tela não exibe a captura da camera.
4- Se pressiono o botao de foto vem a mensagem dizendo que a foto foi gravada, mas não tem imagem em função da surfaceview não ser atualizada.
Você teria ideia do que pode ser? Os códigos estão originais do seu projeto. Estou compilando com a API level 8.
Obrigado

Enviado em 15/01/2013 às 11:05:12 horas, por André


Derci, você usa o startActivity(), e não o startActivityForResult(). Você não captura o retorno da activity. A foto é gravada diretamente no arquivo que você especificou através do extra MediaStore.EXTRA_OUTPUT adicionado à intent. O começo do artigo mostra como isso deve ser feito. Abraço!

Enviado em 05/12/2012 às 17:04:58 horas, por Carlos Tosin


Olá,

Fiz esse procedimento, porém no meu onActivityResult o data está vindo null.

Segue código para chamar a câmera:

File picDir = new File(Global.DIRECTORY_PICTURES);
File imageFile = new File(picDir, gerarNomeFoto());
Intent i = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile));
startActivityForResult(i, TIRAR_FOTO);

Teste feito na versão 3.0 do Android.

Sabe me dizer o que pode ser?

Enviado em 05/12/2012 às 16:23:54 horas, por Derci Santos


Marcelo, você pode chamar o setJpegQuality() no onSurfaceCreated(). Sobre a alteração global desta propriedade, acredito que não é possível. Abraço!

Enviado em 26/10/2012 às 13:32:26 horas, por Carlos Tosin


Carlos, obrigado pelas dicas. Muito elucidativo. Gostaria de saber em que momento pode-se definir o parâmetro "setJpegQuality(int)". Também gostaria de saber se é possível alterar esse parâmetro globalmente, ou seja, definir um valor que será utilizado por todos apps de câmera.
Obrigado!

Enviado em 26/10/2012 às 13:25:23 horas, por Marcelo Ferrari


Márcia, dei uma pesquisada nesse seu problema e a conclusão que eu cheguei é que este tipo de recurso deve ser testado num dispositivo real, uma vez que o emulador não dá suporte à detecção facial. O que é razoável, pois muitos recursos do Android não funcionam no emulador. Abraço!

Enviado em 16/08/2012 às 11:19:06 horas, por Carlos Tosin


Estou a tentar desenvolver uma aplicação para detectar a face, ao utilizar a classe getMaxNumDetectedFaces() ela devolve sempre 0. No AVD eu escolhi webcam0 pois é a unica que dá imagem e estou a utilizar o emulador API14. Sabe-me dizer como resolver? Obrigada.

Enviado em 15/08/2012 às 18:37:07 horas, por Márcia


Leandro, infelizmente isto não é possível. Quando você usa o app nativo da câmera, você não consegue fazer configurações via programação. Isto só é possível quando você usa a classe Camera diretamente.

Enviado em 28/06/2012 às 17:50:28 horas, por Carlos Tosin


Olá Carlos Gostaria de acessar a aplicacao da camera nativa apartir de minha aplicação porém gostaria de setar a resolução para 2M, hoje a aplicação abre com a ultima resolução configurada na camera, teria algum modo de fazer isso?

Enviado em 25/06/2012 às 09:18:55 horas, por Leandro


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

Mailing List

Cadastre o seu e-mail para receber notícias e informações sobre novos cursos, sorteios e outras novidades!

Diferenciais

Liberdade total
Estude quando e como quiser. Disponibilidade do conteúdo 24h por dia, 7 dias por semana.
Matrícula não expira
Pagamento único, sem mensalidades, e acesso vitalício a todo o conteúdo, mesmo após a conclusão do curso.
Cursos sempre atualizados
Acesso às atualizações dos cursos de forma automática.
Tire suas dúvidas
Suporte eficiente para esclarecer suas dúvidas no decorrer do curso.
Padrão de qualidade
Atendimento diferenciado e material de alta qualidade, feito por quem entende do assunto.

Certificado

Insira o código do certificado que deseja consultar:

Pagamento




             Cursos  |   Perguntas  |   Sobre nós  |   Sorteios  |   Blog  |   Política de Privacidade  |   Contato Desde 2003. Todos os direitos reservados ®