Autore Topic: [medio] Usare la webcam del PC al posto della camera negli emulatori Android  (Letto 7954 volte)

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Scopo del tutorial : usare la webcam del PC al posto della camera negli emulatori Android
Livello di difficoltà : medio
Min-SDK : 4 (Android 1.6)


Riconoscimenti: Il tutorial qua presentato è una variante di quello di noodles ([medio] Come utilizzare la Camera di Android per scattare una foto - Android Developers Italia) e si ispira all'idea di Tom Gibara (Obtaining a Live Camera Preview in Android).


Premessa

Sviluppare un'applicazione che faccia uso della Camera è normalmente impossibile attraverso l'emulatore Android, in quanto praticamente la Camera non viene in nessun modo emulata, se non con una superficie quadrettata. L'idea è quella di creare una classe (EmulatedCamera), che sia completamente sostituibile a quella della reale (Camera) e che faccia arrivare all'emulatore il flusso video in tempo reale della webcam del PC. Idealmente alla fine del debug su emulatore dovrebbe bastare sostituire EmulatedCamera con Camera per avere tutto perfettamente funzionante sul dispositivo fisico.


Software esterno

Requisito essenziale per poter collegare la webcam del PC all'emulatore è avere un software che renda disponibile il flusso video della webcam attraverso il protocollo HTTP. Un software freeware che lo permette e che ho usato per il testing è Webcam2000 reperibile a questo indirizzo (WebCam2000 - Download e in allegato). Per esempio, dopo averlo impostato sulla porta 8181 ho provato il tutto aprendo con Firefox l'indirizzo http://192.168.1.8:8181 e verificando che arrivassero le immagini della webcam (192.168.1.8 era il mio IP in rete locale).

Nella classe EmulatedCamera ho messo anche la possibilità di usare un bitmap drawable al posto del flusso video della webcam, per prove su specifiche immagini o in mancanza di webcam.


Funzioni implementate e limiti


La classe EmulatedCamera emula la funzione di Preview della classe Camera, cioè l'anteprima in tempo reale di ciò che vede la cam. E' in grado di ricevere flusso video di qualsiasi risoluzione e adattarlo dinamicamente a qualsiasi risoluzione e orientazione dell'emulatore.

Attualmente non è implementata la funzione takePicture (se qualcuno ha voglia...) e la gestione di gran parte di parameters (perchè non c'è modo di agire sui comandi della webcam del PC). Rispetto al tutorial di noodles ho rimosso due righe relative all'impostazione della risoluzione finale dell'immagine, perchè mi hanno permesso di far funzionare il tutto anche su Android 1.6.

Teoricamente la classe EmulatedCamera potrebbe essere usata anche per trasmettere le immagini di una qualsiasi webcam ad un dispositivo fisico Android collegato in rete. Da provare.

Emulatore Android 1.6 240x320 - webcam 240x320


Emulatore Android 2.1 320x480 - webcam 480x640



Tutorial

Nel manifest oltre alla CAMERA occorre sia impostata anche la rete INTERNET per poter ricevere il flusso video.
Codice (XML): [Seleziona]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.bradipao.emucam"
     android:versionCode="1"
     android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".EmuCam"
                 android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

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

Il layout è sostanzialmente lo stesso del tutorial di noodles.
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: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>


L'activity principale EmuCam è quasi uguale a quella del tutorial di noodles, con le seguenti differenze:
  • Si usa la EmulatedCamera al posto della Camera
  • Il SurfaceHolder deve essere di tipo SURFACE_TYPE_NORMAL
  • Rimossa l'impostazione della risoluzione di acquisizione per farlo girare anche sull'1.6
  • Aggiunta funzione membro setSource alla camera per impostare l'indirizzo http del flusso video

Codice (Java): [Seleziona]
package com.bradipao.emucam;

import java.io.IOException;

import android.app.Activity;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.PictureCallback;
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 EmuCam extends Activity implements SurfaceHolder.Callback {

   // oggetti e variabili
   private SurfaceView mSurfaceView;
   private SurfaceHolder mSurfaceHolder;
   private boolean mPreviewRunning;
   
   // sull'emulatore usare EmulatedCamera al posto di Camera
   private EmulatedCamera mCamera;
   
   /** Called when the activity is first created. */
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);

      // impostazione per il layout
      getWindow().setFormat(PixelFormat.TRANSLUCENT);
      requestWindowFeature(Window.FEATURE_NO_TITLE);
      getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);   //full screen
      setContentView(R.layout.main);
     
      // (attualmente non usato)
      // imagebutton
      ImageButton buttonPicture = (ImageButton) findViewById(R.id.camera_surface_button);
      buttonPicture.setOnClickListener(new OnClickListener() {
         public void onClick(View v) {
            // non ancora implementato nella EmulatedCamera
            // mCamera.takePicture(null, null, jpegCallback);
         }
      });
     
      // SurfaceView e SurfaceHolder
      mSurfaceView = (SurfaceView)findViewById(R.id.camera_surface);
      mSurfaceHolder = mSurfaceView.getHolder();
      mSurfaceHolder.addCallback(this);

      // sull'emulatore usare SURFACE_TYPE_NORMAL al posto di SURFACE_TYPE_PUSH_BUFFERS
      mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
   }
   
   // (attualmente non usato)
   // callback richiamata quando l'immagine è pronta per essere memorizzata
   PictureCallback jpegCallback = new PictureCallback() {
      public void onPictureTaken(byte[] _data,Camera _camera) {
         // salvataggio immagine
         // riparte la preview
         mCamera.startPreview();
      }
   };

   // override di SurfaceChange, si reimpostano le dimensioni della view
   @Override
   public void surfaceChanged(SurfaceHolder arg0,int arg1,int arg2,int arg3) {
      // stop della preview
      if (mPreviewRunning) mCamera.stopPreview();
      // setto le preferenze partendo da quelle di default
      Camera.Parameters p = mCamera.getParameters();
      p.setPreviewSize(arg2,arg3);
      mCamera.setParameters(p);
      // rilancio la preview
      try {
         mCamera.setPreviewDisplay(arg0);
         mCamera.startPreview();
         mPreviewRunning = true;
      } catch (IOException e) {
         Log.i("EMUCAM","IOException"+e.toString());
      }
   
   }

   // override di SurfaceCreated, si crea la EmulatedCamera e si imposta la sorgente di emulazione
   @Override
   public void surfaceCreated(SurfaceHolder holder) {
      mCamera = EmulatedCamera.open();
      // il seguente metodo serve solo per la EmulatedCamera (da togliere per Camera)
      // (A) questo per ricevere il flusso video http da una webcam
      mCamera.setSource("http://192.168.1.8:8181");
      // (B) questo per usare una drawable come sorgente del flusso video
      // mCamera.setSource(getResources(),R.drawable.fotogramma);
   }

  // override di SurfaceDestroyed, si ferma la preview e si rilascia la camera
   @Override
   public void surfaceDestroyed(SurfaceHolder holder) {
      mCamera.stopPreview();
      mPreviewRunning = false;
      mCamera.release();
   }
}

La classe EmulatedCamera è progettata per emulare al meglio la rispettiva classe del device reale Camera. Nella prima parte sono riprodotte molto semplicemente le funzioni di base open(), startPreview(), stopPreview(), setPreviewDisplay(), setParameters() e getParameters(), release().

Il cuore della classe è un Thread di nome CameraCapture, che viene creato a partire dalla startPreview() e distrutto sulla stopPreview(). Una volta creato, il Thread gira indefinitamente e le operazioni che compie sono:
1) preleva il prossimo fotogramma dalla fonte video (sia una webcam in rete o una bitmap drawable)
2) scalare e disegnare il fotogramma sul canvas del nostro SurfaceHolder
3) torna a 1

Codice (Java): [Seleziona]
package com.bradipao.emucam;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.util.Log;
import android.view.SurfaceHolder;


public class EmulatedCamera {

   // costanti
   private static final int CONN_TIMEOUT = 1000;
   private static final int READ_TIMEOUT = 1000;

   // classi
   static private EmulatedCamera emCamera;
   private CameraCapture capture;
   private Camera parametersCamera;
   private SurfaceHolder surfaceHolder;

   // sorgente video
   private String sUrl = "";
   private Resources rRes = null;
   private int iRes = 0;
   
   // variabili
   private final boolean preserveAspectRatio = true;
   private final Paint paint = new Paint();
   private int width = 240;
   private int height = 320;
   private Rect bounds = new Rect(0, 0, width, height);
 
   // costruttore
   private EmulatedCamera() {
      // serve solamente a popolare correttamente parametersCamera
      parametersCamera = Camera.open();
   }
 
   // imposto la sorgente video su un URL o una immagine delle risorse
   public void setSource(String sUrl) {
      this.sUrl = sUrl;
      this.iRes = 0;
   }

   public void setSource(Resources rRes,int iRes) {
      this.sUrl = "";
      this.rRes = rRes;
      this.iRes = iRes;
   }  
   
   // open : se non esiste, creo l'istanza
   static public EmulatedCamera open() {
      if (emCamera == null) emCamera = new EmulatedCamera();
      Log.i("EMULATEDCAM", "Creating Emulated Camera");
      return emCamera;
   }

   // startPreview : creo il thread che genera il flusso video
   public void startPreview() {
      capture = new CameraCapture();
      capture.setCapturing(true);
      capture.start();
      Log.i("EMULATEDCAM", "Starting Emulated Camera");
   }
 
   // stopPreview : stop al thread che genera il flusso video
   public void stopPreview() {
      capture.setCapturing(false);
      Log.i("EMULATEDCAM", "Stopping Emulated Camera");
   }
 
   // setPreviewDisplay : imposto il surfaceHolder
   public void setPreviewDisplay(SurfaceHolder surfaceHolder) throws IOException {
      this.surfaceHolder = surfaceHolder;
   }
 
   // setParameters : dei parametri da settare quello importante è la size della preview
   public void setParameters(Camera.Parameters parameters) {
      Log.i("EMULATEDCAM", "Setting Emulated Camera parameters");
      parametersCamera.setParameters(parameters);
      Size size = parameters.getPreviewSize();
      bounds = new Rect(0,0,size.width,size.height);
   }
   
   // getParameters : rilettura dei parametri
   public Camera.Parameters getParameters() {
      Log.i("EMULATEDCAM", "Getting Emulated Camera parameters");
      return parametersCamera.getParameters();
   }
 
   
   public void release() {
      Log.i("EMULATEDCAM", "Releasing Emulated Camera parameters");
      // il release non ha molto senso, perchè al contrario della vera camera non è una risorsa unica
   }
 
   // THREAD PER GENERARE IL FLUSSO VIDEO
   // interno alla classe
   private class CameraCapture extends Thread  {
 
      // attivazione-disattivazione del thread con relativa variabile
      private boolean capturing = false;
      public void setCapturing(boolean capturing) {
         this.capturing = capturing;
      }

      // loop del thread
      @Override
      public void run() {
         while (capturing) {
            Canvas c = null;
            try {
               c = surfaceHolder.lockCanvas(null);
               synchronized (surfaceHolder) {

                  try {

                     // vars
                     Bitmap bitmap = null;
                     InputStream in = null;
                     int response = -1;

                     // se è una bitmap nelle risorse, uso quella
                     if (iRes!=0) {
                        bitmap = BitmapFactory.decodeResource(rRes,iRes);  
                     }
                     // altrimenti se è un URL inizializzo la connessione http
                     else if (sUrl.length()>10) {                      
                        URL url = new URL(sUrl);
                        URLConnection conn = url.openConnection();
                        if (!(conn instanceof HttpURLConnection)) throw new IOException("Not an HTTP connection.");
                        HttpURLConnection httpConn = (HttpURLConnection) conn;
                        httpConn.setAllowUserInteraction(false);
                        httpConn.setConnectTimeout(CONN_TIMEOUT);
                        httpConn.setReadTimeout(READ_TIMEOUT);
                        httpConn.setInstanceFollowRedirects(true);
                        httpConn.setRequestMethod("GET");
                        httpConn.connect();
                        // gestione risposta
                        response = httpConn.getResponseCode();
                        if (response == HttpURLConnection.HTTP_OK) {
                           in = httpConn.getInputStream();
                           bitmap = BitmapFactory.decodeStream(in);
                        }
                     }
               
                     // se le dimensioni non coincidono, scalo il flusso per la nostra preview
                     if (bounds.right==bitmap.getWidth() && bounds.bottom == bitmap.getHeight()) {
                        c.drawBitmap(bitmap,0,0,null);
                     } else {
                        Rect dest;
                        if (preserveAspectRatio) {
                           dest = new Rect(bounds);
                           dest.bottom = bitmap.getHeight() * bounds.right / bitmap.getWidth();
                           dest.offset(0, (bounds.bottom - dest.bottom)/2);
                        } else {
                           dest = bounds;
                        }
                        if (c != null) c.drawBitmap(bitmap,null,dest,paint);
                     }

                  } catch (RuntimeException e) {
                     e.printStackTrace();
                  } catch (IOException e) {
                     e.printStackTrace();
                  }  
               }
            } catch (Exception e) {
               e.printStackTrace();
            } finally {
               // se viene generata una eccezione, si rilascia il surface
               if (c != null) surfaceHolder.unlockCanvasAndPost(c);
            }
         }
         Log.i("EMULATEDCAM", "Emulated Camera Thread stopped");
      }
   }

}







NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline enos

  • Nuovo arrivato
  • *
  • Post: 28
  • Respect: 0
    • Mostra profilo
Re:[medio] Usare la webcam del PC al posto della camera negli emulatori Android
« Risposta #1 il: 08 Settembre 2010, 13:22:34 CEST »
0
Ciao a tutti,
ho seguito il tutorial e l'unico errore che ricevo è sull'Override di SurfaceChanged, SurfaceCreated e SurfaceDestroyed.
Se cancello la direttiva @Override prima dei metodi la simulazione parte ma ricevo sempre schermata nera sul simulatore.

Avete qualche suggerimento?
Grazie  :-)

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:[medio] Usare la webcam del PC al posto della camera negli emulatori Android
« Risposta #2 il: 08 Settembre 2010, 14:52:27 CEST »
0
Ti premetto che nello scrivere questa applicazione e cercare di farla funzionare, inizialmente anche io avevo solo schermate nere, per cui ti suggerisco alcuni modi di debuggare il problema.

1) Per verificare che il flusso di video della webcam sia effettivamente generato e visibile, apri l'indirizzo nel tuo browser. Esempio io che avevo IP 192.168.1.8 e porta 8181 inserisco http://192.168.1.8:8181 nella barra indirizzi del browser e mi arrivano le JPG della webcam in tempo reale.

2) Per verificare la corretta esecuzione dell'applicazione, è possibile usare al posto del flusso video della webcam, una bitmap inserita nelle risorse grafiche. In fondo al sorgente della activity togli il commento da mCamera.setSource(getResources(),R.drawable.fotogramma); e mettilo a mCamera.setSource("http://192.168.1.8:8181"); Così facendo provi tutto il codice "grafico" della activity escludendo la ricezione da webcam.
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline enos

  • Nuovo arrivato
  • *
  • Post: 28
  • Respect: 0
    • Mostra profilo
Re:[medio] Usare la webcam del PC al posto della camera negli emulatori Android
« Risposta #3 il: 10 Settembre 2010, 10:38:33 CEST »
0
Ciao bradipao,
il problema degli Override è legato al fatto che i metodi SurfaceChanged, SurfaceCreated e SurfaceDestroyed sono dell'interfaccia SurfaceHolder.Callback: vanno semplicemente implementati, per questo da errore sull'@Override.
Non mi spiego come a te funzioni!  :-o

A parte questo evidentemente avevo commesso qualche altro errore senza accorgermene: adesso tutto funziona!
Grazie mille per il tutorial!  ;-)

Offline bradipao

  • Moderatore globale
  • Utente storico
  • *****
  • Post: 4043
  • keep it simple
  • Respect: +567
    • Github
    • Google+
    • bradipao
    • Mostra profilo
  • Dispositivo Android:
    Nexus 5
  • Play Store ID:
    Bradipao
  • Sistema operativo:
    W7
Re:[medio] Usare la webcam del PC al posto della camera negli emulatori Android
« Risposta #4 il: 10 Settembre 2010, 10:46:45 CEST »
0
Ciao bradipao,
il problema degli Override è legato al fatto che i metodi SurfaceChanged, SurfaceCreated e SurfaceDestroyed sono dell'interfaccia SurfaceHolder.Callback: vanno semplicemente implementati, per questo da errore sull'@Override.
Non mi spiego come a te funzioni!  :-o

In tutta sincerità, io sono partito dal tutorial originario di noodles apportando meno modifiche possibile. Comunque è molto importante che tu abbia postato la soluzione al problema, per chi dovesse incontrarlo in futuro. Grazie.

A parte questo evidentemente avevo commesso qualche altro errore senza accorgermene: adesso tutto funziona!
Grazie mille per il tutorial!  ;-)

Il fatto che tutto funzioni perfettamente mi fa molto piacere. Significa che a qualcuno è servito.  :-P

Se farai aggiunte/modifiche e se avrai voglia, sei il benvenuto su questo thread per aggiungere ulteriori commenti, modifiche o anche versioni migliorate.
NON rispondo a domande nei messaggi privati
Bradipao @ Play Store

Offline rookie

  • Nuovo arrivato
  • *
  • Post: 1
  • Respect: 0
    • Mostra profilo
  • Dispositivo Android:
    Nexus 4
  • Sistema operativo:
    Windows 7
Re:[medio] Usare la webcam del PC al posto della camera negli emulatori Android
« Risposta #5 il: 29 Ottobre 2013, 12:06:51 CET »
0
Ciao,

sto diventando pazzo per puntare ad una web cam esterna. Pensavo che questo esempio facesse a caso mio.
Così ho creato un nuovo progetto e ho copiato il codice modificato l'indirizzo in mCamera.setSource() con il mio:
"http://xxxxxx/videostream.cgi?user=admin&pwd=xxx".

Ma appena lancio l'applicazione mi da i seguenti errori e si chiude:

10-29 11:48:43.567: E/AndroidRuntime(6354): FATAL EXCEPTION: main
10-29 11:48:43.567: E/AndroidRuntime(6354): java.lang.NullPointerException
10-29 11:48:43.567: E/AndroidRuntime(6354):    at com.example.camera.EmulatedCamera.getParameters(EmulatedCamera.java:102)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at com.example.camera.EmuCam.surfaceChanged(EmuCam.java:77)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.SurfaceView.updateWindow(SurfaceView.java:1137)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.SurfaceView.access$000(SurfaceView.java:88)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:186)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:590)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1642)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2467)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.os.Handler.dispatchMessage(Handler.java:99)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.os.Looper.loop(Looper.java:137)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at android.app.ActivityThread.main(ActivityThread.java:4424)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at java.lang.reflect.Method.invokeNative(Native Method)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at java.lang.reflect.Method.invoke(Method.java:511)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
10-29 11:48:43.567: E/AndroidRuntime(6354):    at dalvik.system.NativeStart.main(Native Method)


Bradipo sai aiutarmi? Perchè va in crash?
Grazie.
« Ultima modifica: 29 Ottobre 2013, 14:12:59 CET da rookie »