Autore Topic: [medio] Come utilizzare la Camera di Android per scattare una foto  (Letto 34273 volte)

Offline noodles

  • Utente junior
  • **
  • Post: 130
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Google Nexus One
  • Sistema operativo:
    Mac OS X Snow Leopard
+4
Livello di difficoltà: medio
Versione SDK utilizzata: 2.1
Link al file compresso del progetto eclipse: file in allegato

In questo tutorial voglio farvi vedere come fare ad avere accesso alla Camera messa a disposizione del vostro device e come poter con questa scattare una foto. Il tutorial si concentra sulle funzionalità base messe a disposizione da android, vuole solo essere un trampolino di lancio, lasciando allo sviluppatore ulteriori accorgimenti per arricchire e migliorare la propria app.

Per iniziare avremo bisogno di una SurfaceView ed una interfaccia SurfaceHolder. Vediamo brevemente di cosa si tratta.
Una SurfaceView, come dice il nome, è una particolare View che permette di disegnare sul display elementi grafici, nel nostro caso la preview proveniente dalla camera. SurfaceHolder è un'interfaccia che ci permette di gestire la SurfaceView, mediante l'utilizzo di particolare operazioni (callback), che ci serviranno per capire quando la Surface verrà creata, quando subirà cambiamenti e quando verrà distrutta.
Utilizzeremo anche la classe Camera, che ci permette di interagire direttamente con il dispositivo hardware del device. La utilizzeremo per occupare la risorsa camera, per rilasciarla, per scattare una foto, per far partire la preview, per settare i parametri come autofocus, flash etc... questi ultimi due avranno bisogno di un tutorial a parte che provvederò a fornire prossimamente.
Infine dovremo utilizzare un'ulteriore interfaccia Camera.PictureCallback per definire le operazioni da fare quando verrà scattata la foto.

Questi sono gli elementi essenziali. Partiamo con il tutorial vero e proprio.  :-)

Per prima cosa creariamo una nostra activity che estenda Activity e (importantissimo!) implementi SurfaceHolder.Callback, altrimenti non saremmo in grado di utilizzare le callback per gestire la surface.
Quello che vogliamo è un'activity che mostri a tutto schermo la camera con un pulsante dedicato per far partire lo scatto. Quindi creiamo la nostra Activity e informiamo Android che vogliamo settare alcuni parametri come essere a full screen e senza la barra di stato.
Codice (Java): [Seleziona]
getWindow().setFormat(PixelFormat.TRANSLUCENT); //aggiungo il traslucido
requestWindowFeature(Window.FEATURE_NO_TITLE);  //no barra titolo
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);   //full screen

Fatto questo recuperiamo la nostra SurfaceView il la nostra SurfaceHolder e diciamo ad Android di collegarla alla nostra Activity. In questo modo la nostra activity risponderà alle chiamate a callback della surface.
Codice (Java): [Seleziona]
mSurfaceView = (SurfaceView)findViewById(R.id.camera_surface);   //recupero il riferimento alla SurfaceView da XML
mSurfaceHolder = mSurfaceView.getHolder();      //recupero l'holder della SurfaceView
mSurfaceHolder.addCallback(this);       //faccio la bind con la nostra activity
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);        // setto tipo di surface, suggerito nei tutorial ufficiali

A questo punto dobbiamo implementare i 3 metodi dell'interfaccia SurfaceHolder: surfaceCreated(), surfaceChanged, surfaceDestroyed(). Tutte e 3 hanno come parametro la SurfaceHolder collegata alla surface creata.
Codice (Java): [Seleziona]
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
        if (mPreviewRunning)
                mCamera.stopPreview();
               
        //setto le preferenze
        Camera.Parameters p = mCamera.getParameters();  //prendo le preferenze della camera
        p.setPreviewSize(arg2, arg3);
        ArrayList<Size> list = (ArrayList<Size>) p.getSupportedPictureSizes();  //recuepro le risoluzioni supportate dalla camera
        int picture_width = list.get(list.size()-1).width;
        int picture_height = list.get(list.size()-1).height;
        p.setPictureSize(picture_width, picture_height);        //setto la camera alla risoluzione più bassa
        p.setJpegQuality(80);   // qualità compressione JPEG
   
        // salvo le pref
        mCamera.setParameters(p);
        try {
                //lancio la preview
                mCamera.setPreviewDisplay(arg0);       
                mCamera.startPreview();
                mPreviewRunning = true;
        } catch (IOException e) {
                        //gestione errore
        }
       
               
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
        mCamera = Camera.open();               
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
        mPreviewRunning = false;
        mCamera.release();

}

La surfaceCreated viene chiama appena la surface viene creata. Qui dobbiamo semplicemente ottenere l'accesso alla Camera con la open().
La surfaceDestroyed viene chiamata quando la surface o l'activity vengono distrutte. Non facciamo altro che fermare la preview sulla surface e rilasciare la camera (importante).
Nella surfaceChanged che viene chiamata ogni volta che la surface subisce un cambiamento, vengono effettuati i settaggi dei paramentri della camera. E' consigliato effettuare questi settaggi solo quando la preview non è partita, in modo da evitare flickering o crash improvvisi.
La setPreviewSize() mi setta la dimensione di quello che saranno i frames visti a video secondo width e heigth forniti in automatico dalla callback come parametri.
La getSupportedPictureSize restituisce le risoluzioni supportate dalla camera del nostro device. La successiva chiamata a setPictureSize() non fa altro che definire a quale risoluzione vogliamo scattare la foto. Per comodità io l'ho impostata alla più bassa, ma ognuno è ovviamente libero di decidere quale utilizzare.
La setQualityJpeg() credo sia intuitiva... il valore 80 è quello solitamente utilizzato.
Infine coe startPreview() si fa partire la preview sulla nostra surface e visulizziamo cosa la camera sta riprendendo.

Mancano ancora due pezzi del puzzle per concludere il nostro tutorial. Come si fa a scattare la foto?

Ora definiamo l'evento onClick sul pulsante per scattare.
Codice (Java): [Seleziona]
ImageButton buttonPicture = (ImageButton) findViewById(R.id.camera_surface_button);
                buttonPicture.setOnClickListener(new OnClickListener(){
                        public void onClick(View v) {
                                mCamera.takePicture(null, null, jpegCallback);  
                        }
                });

Come vedete è presente un metodo d'istanza di Camera, che si occuperà di fare la foto e che utilizza una serie di callback che vengono chiamate durante lo scatto. E' a discrezione del programmatore utilizzarle a seconda del bisogno. Per approfonditmenti vi rimando alla documentazione ufficiale. A noi interessa l'ultima, che io ho chiamato jpegCallback, che non è altro che la definizione dell'interfaccia Camera.PictureCallback a cui accennavo prima.
Codice (Java): [Seleziona]
PictureCallback jpegCallback = new PictureCallback() {
                public void onPictureTaken(byte[] _data, Camera _camera) {
                        //riparte la preview della camera
                        mCamera.startPreview();
                       
                }
        };

L'evento onPictureTaken fornisce tra i paramentri l'array di byte, che mi rappresenta l'immagine scattata. Sta a noi programmatori decide come utilizzarla, per esempio è possibile salvarla sulla SD card e magari decidere di renderla visibile nella gallery... infine va di nuovo chiamata la startPreview() per far ripartire la camera e renderla nuovamente disponibile a successive foto.  ;-)

Ci sarebbero ancora diverse cose da dire, come per esempio la gestione dell'autofocus o del flash... ma credo che questo sia un buon punto da cui partire.

Vi ricordo che nel Manifest andrà inserito il relativo permesso per utilizzare la Camera
Codice (XML): [Seleziona]
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera"/>
Il primo indica che l'app utilizzerà la CAMERA. Se lasciassi solo questo la mia app avrebbe accesso a tutte le funzionalità della camera del device su cui la mia app è installata, rendola incompatibile se facessei uso a programma di features che non sono supportate dal device. Se invece specifico le features una per una sono più selettivo e indico effettivamente quelle utilizzate. Se invece mi limito a fare come nel tutorial, indicando cioè solo la feature generica android.hardware.camera, dico che non uso nessuna features particolare e rendo la mia app altamente compatibile. Sta a voi anche qui decidere il giusto compromesso a seconda delle necessità.
Maggiori info a riguardo le trovate come sempre nella documentazione ufficiale. QUI.


Sorgenti:

CameraView.java
Codice (Java): [Seleziona]
import java.io.IOException;
import java.util.ArrayList;

import android.app.Activity;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.Size;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnClickListener;
import android.widget.ImageButton;

public class CameraView extends Activity implements SurfaceHolder.Callback{
       
/* VARIABILI PRIVATE */
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Camera mCamera;
private boolean mPreviewRunning;
       
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getWindow().setFormat(PixelFormat.TRANSLUCENT); //aggiungo il traslucido
        requestWindowFeature(Window.FEATURE_NO_TITLE);  //no barra titolo
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);   //full screen
        setContentView(R.layout.main);
        ImageButton buttonPicture = (ImageButton) findViewById(R.id.camera_surface_button);
                buttonPicture.setOnClickListener(new OnClickListener(){
                        public void onClick(View v) {
                                mCamera.takePicture(null, null, jpegCallback);
                        }
                });
               
        mSurfaceView = (SurfaceView)findViewById(R.id.camera_surface);
        mSurfaceHolder = mSurfaceView.getHolder();      //recupero l'holder della surfaceview
        mSurfaceHolder.addCallback(this);       //faccio la bind alla nostra activity
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);        //tipo di surface, suggerito nei tutorial ufficiali
    }
   
   
   
    PictureCallback jpegCallback = new PictureCallback() {
                public void onPictureTaken(byte[] _data, Camera _camera) {
                        //riparte la preview della camera
                        mCamera.startPreview();
                       
                }
        };

        @Override
        public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
                if (mPreviewRunning)
                        mCamera.stopPreview();
               
                //setto le preferenze
                Camera.Parameters p = mCamera.getParameters();  //prendo le preferenze della camera
                p.setPreviewSize(arg2, arg3);
                ArrayList<Size> list = (ArrayList<Size>) p.getSupportedPictureSizes();  //recuepro le risoluzioni supportate dalla camera
        int picture_width = list.get(list.size()-1).width;
        int picture_height = list.get(list.size()-1).height;
        p.setPictureSize(picture_width, picture_height);        //setto la camera alla risoluzione più bassa
        p.setJpegQuality(80);   // qualità compressione JPEG
       
        // salvo le pref
        mCamera.setParameters(p);
        try {
                //lancio la preview
                        mCamera.setPreviewDisplay(arg0);       
                        mCamera.startPreview();
                        mPreviewRunning = true;
                } catch (IOException e) {
                        //gestione errore
                }
       
               
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
                mCamera = Camera.open();
               
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
        mCamera.stopPreview();
        mPreviewRunning = false;
        mCamera.release();
               
        }
}


AndroidManifest.xml
Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.tutorial.camera"
     android:versionCode="1"
     android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".CameraView"
                 android:label="@string/app_name"
                 android:screenOrientation="landscape">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
    <uses-sdk android:minSdkVersion="7" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera"/>

</manifest>

main.xml
Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
       
                <SurfaceView android:id="@+id/camera_surface"
                        android:layout_width="fill_parent"
                        android:layout_height="fill_parent"
                        android:layout_weight="1">
                </SurfaceView>
               
                <RelativeLayout android:id="@+id/camera_surface_footer"
                        android:layout_width="wrap_content"
                        android:layout_height="fill_parent"
                        android:background="#E4E4E4"
                        android:layout_alignRight="@id/camera_surface">
                       
                        <ImageButton android:id="@+id/camera_surface_button"   
                                android:layout_width="wrap_content"
                                android:layout_height="wrap_content"
                                android:src="@drawable/icon"
                                android:layout_centerInParent="true"
                                />
                </RelativeLayout>

</RelativeLayout>

Spero di aver fatto cosa gradita e che questa guida possa risultarvi utile.
« Ultima modifica: 02 Aprile 2010, 18:45:25 CEST da noodles »

Offline dodopepper

  • Utente junior
  • **
  • Post: 124
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32a black
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #1 il: 13 Aprile 2010, 16:35:29 CEST »
0
Ciao! Innanzitutto grazie mille per questo utilissimo tutorial. Vorrei sottoporti un quesito. Ho provato a lanciare la tua applicazione sull'emulatore ma mi da force closed mentre sul mio dispositivo fisico (un magic) funziona egregiamente. Vorrei sapere come risolvere questo problema! Grazie mille.

Offline noodles

  • Utente junior
  • **
  • Post: 130
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Google Nexus One
  • Sistema operativo:
    Mac OS X Snow Leopard
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #2 il: 13 Aprile 2010, 22:00:21 CEST »
0
ciao! Sinceramente non ho testato la mia app sull'emulatore, ma solo sul Nexus One.
Non ho idea di perchè non funzioni, ma a pensarci ben non so come sia possibile sull'emulatore utilizzare la risorsa Camera... :|

Offline dodopepper

  • Utente junior
  • **
  • Post: 124
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32a black
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #3 il: 13 Aprile 2010, 22:17:49 CEST »
0
In effetti ho avuto lo stesso pensiero anche io! Visto che sei così disponibile posso porti ancora qualche domanda? Sono davvero alle prime armi con android e di java so poco o niente, all'uni ho studiato solo c++ e dovrei fare un'applicazione android per la tesi.

Vorrei sapere a tal proposito(prova dell'applicazione) se c'è un modo veloce per caricare l'applicazione sul telefono, magari direttamente da eclipse come si fa con l'emulatore. In modo da evitare di fare l'installazione da cmd usando l'adb! Grazie mille!

Offline JD

  • Amministratore
  • Utente storico
  • *****
  • Post: 1600
  • Respect: +232
    • leinardi
    • Mostra profilo
  • Dispositivo Android:
    LG Nexus 5
  • Sistema operativo:
    L'ultima Ubuntu
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #4 il: 13 Aprile 2010, 23:42:49 CEST »
0
Se adb riconosce il tuo telefono, quando su eclipse manderai in play, l'applicazione verrà automaticamente installata sul telefono. Se hai in esecuzione uno o più emulatori ti chiederà dove vuoi la vuoi installare ;).
È stata trovata una soluzione al tuo problema?
Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato ;).
E se hai aperto tu il thread marcalo come risolto cliccando !

Offline noodles

  • Utente junior
  • **
  • Post: 130
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Google Nexus One
  • Sistema operativo:
    Mac OS X Snow Leopard
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #5 il: 14 Aprile 2010, 17:16:54 CEST »
0
Se adb riconosce il tuo telefono, quando su eclipse manderai in play, l'applicazione verrà automaticamente installata sul telefono. Se hai in esecuzione uno o più emulatori ti chiederà dove vuoi la vuoi installare ;).

perfetto ti ha già risposto JD ;)

Offline dodopepper

  • Utente junior
  • **
  • Post: 124
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32a black
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #6 il: 14 Aprile 2010, 19:16:55 CEST »
0
Grazie mille ragazzi! In effetti provare l'applicazione direttamente su terminale è molto meglio che sull'emulatore!

Offline dodopepper

  • Utente junior
  • **
  • Post: 124
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32a black
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #7 il: 15 Aprile 2010, 12:48:03 CEST »
0
Salve gente, ho  ancora un problema, sto provando a smanettare un po' ma non ogni volta che provo a lanciare il preview con il metod addCalback (this) mi va in crash. Ci sto perdendo la testa e non so dov'è il problema. Potreste dare uno sguardo al codice? Lo allego in uno zip

Offline dodopepper

  • Utente junior
  • **
  • Post: 124
  • Respect: +4
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32a black
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #8 il: 15 Aprile 2010, 15:31:05 CEST »
0
Non so come, non so perchè ma ora l'applicazione parte! chissà che cavolo combinavo? Ma che eclipse usate? Io uso ganymede, forse è meglio galileo?

Offline JD

  • Amministratore
  • Utente storico
  • *****
  • Post: 1600
  • Respect: +232
    • leinardi
    • Mostra profilo
  • Dispositivo Android:
    LG Nexus 5
  • Sistema operativo:
    L'ultima Ubuntu
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #9 il: 15 Aprile 2010, 15:32:00 CEST »
0
Eh, fossi in te aggiornerei a Galileo :)
È stata trovata una soluzione al tuo problema?
Evidenzia il post più utile premendo . È un ottimo modo per ringraziare chi ti ha aiutato ;).
E se hai aperto tu il thread marcalo come risolto cliccando !

Offline daniele

  • Nuovo arrivato
  • *
  • Post: 17
  • Respect: +1
    • Mostra profilo
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #10 il: 11 Maggio 2010, 14:00:43 CEST »
0
Ottima guida, però non avendo ancora un telefono Android, non posso provarla. Sapete se è possibile abilitare l'emulatore all'uso della web-cam già presente nel pc?
Ciao a tutti e continuate così.

Offline Vytek

  • Translate Team
  • Utente junior
  • **
  • Post: 125
  • Respect: +6
    • Mostra profilo
  • Dispositivo Android:
    Samsung S5
  • Sistema operativo:
    Windows 8.1
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #11 il: 08 Giugno 2010, 22:34:38 CEST »
0
Vedo che il tutorial è per SDK 2.1, esiste nulla per SDK 1.5? Si può utilizzare magari le stesse classi ma utilizzandole per SDK 1.5?

Un saluto e grazie dell'eventuale risposta...

Offline noodles

  • Utente junior
  • **
  • Post: 130
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Google Nexus One
  • Sistema operativo:
    Mac OS X Snow Leopard
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #12 il: 09 Giugno 2010, 00:53:10 CEST »
0
se non ricordo male cambiava la gestione dei parametri della Camera, tipo che molti metodi erano proprio assenti. Ora non ricordo se si trattasse della 1.5 o della 1.6. Per quanto invece riguarda la struttura generale della surfaceView e dell'utilizzo dell'oggetto Camera, dovrebbe non cambiare nulla.

Offline dersew

  • Nuovo arrivato
  • *
  • Post: 28
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    HTC Magic 32A
  • Sistema operativo:
    ubuntu 10.04
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #13 il: 19 Luglio 2010, 22:05:42 CEST »
0
salve, sul mio magic con Froyo purtroppo nn funziona benissimo, o meglio, il programma parte, si avvia, scatta la foto..ma 1) nn visualizza la foto scattata, 2) nn salva la foto..è normale cioè che si devono implementare le 2 cose nel codice, o c'è qualcosa che nn va?

Offline noodles

  • Utente junior
  • **
  • Post: 130
  • Respect: +8
    • Mostra profilo
  • Dispositivo Android:
    Google Nexus One
  • Sistema operativo:
    Mac OS X Snow Leopard
Re:[medio] Come utilizzare la Camera di Android per scattare una foto
« Risposta #14 il: 19 Luglio 2010, 22:12:22 CEST »
0
Ciao!

Allora con Froyo credo che siano cambiate alcune cose, dico "credo" perchè non ho ancora avuto modo di vedere, ma mi pare che la classe Camera abbia subito modifiche, quindi ci sta che vada rivisto qualcosa. A giorni avrò modo di fare l'upgrade e vi farò sapere.

Il salvataggio invece ovviamente va implementato a mano...