web-dev-qa-db-fra.com

Prendre une photo de l'appareil photo sans aperçu

J'écris une application Android 1.5 qui démarre juste après le démarrage. Il s'agit d'un Service et devrait prendre une photo sans aperçu. Cette application enregistrera la densité lumineuse dans certains J'ai pu prendre une photo mais la photo était noire.

Après avoir fait des recherches pendant longtemps, je suis tombé sur un fil de bogue à ce sujet. Si vous ne générez pas d'aperçu, l'image sera noire car Android l'appareil photo a besoin d'un aperçu pour configurer l'exposition et la mise au point. J'ai créé un SurfaceView et l'écouteur, mais l'événement onSurfaceCreated() n'est jamais déclenché.

Je suppose que la raison en est que la surface n'est pas créée visuellement. J'ai également vu quelques exemples d'appels statiques de l'appareil photo avec MediaStore.CAPTURE_OR_SOMETHING Qui prend une photo et enregistre dans le dossier souhaité avec deux lignes de code, mais il ne prend pas de photo aussi.

Dois-je utiliser IPC et bindService() pour appeler cette fonction? Ou existe-t-il une autre méthode pour y parvenir?

57
eyurdakul

il est vraiment bizarre que la caméra sur Android ne puisse pas diffuser de vidéo jusqu'à ce qu'elle ait une surface d'aperçu valide. Il semble que les architectes de la plate-forme ne pensaient pas du tout aux applications de streaming vidéo tierces. même dans le cas de la réalité augmentée, l'image peut être présentée comme une sorte de substitution visuelle, et non comme un flux de caméra en temps réel.

de toute façon, vous pouvez simplement redimensionner la surface d'aperçu à 1x1 pixels et la placer quelque part dans le coin du widget (élément visuel). veuillez faire attention - redimensionnez la surface d'aperçu, pas la taille du cadre de la caméra.

bien sûr, une telle astuce n'élimine pas le streaming de données indésirables (pour l'aperçu) qui consomme certaines ressources système et batterie.

49

J'ai trouvé la réponse à cela dans le Android Camera Docs .

Remarque: Il est possible d'utiliser MediaRecorder sans créer d'abord un aperçu de la caméra et ignorer les premières étapes de ce processus. Cependant, comme les utilisateurs préfèrent généralement voir un aperçu avant de commencer un enregistrement, ce processus n'est pas abordé ici.

Vous pouvez trouver les instructions étape par étape sur le lien ci-dessus. Après les instructions, il indiquera le devis que j'ai fourni ci-dessus.

38

En fait, c'est possible, mais vous devez simuler l'aperçu avec une SurfaceView factice

SurfaceView view = new SurfaceView(this);
c.setPreviewDisplay(view.getHolder());
c.startPreview();
c.takePicture(shutterCallback, rawPictureCallback, jpegPictureCallback);

Mise à jour 21/09/11: Apparemment, cela ne fonctionne pas pour tous les appareils Android.

36
Frank

Prendre la photo

Faites d'abord fonctionner cela avant d'essayer de masquer l'aperçu.

  • Configurer correctement l'aperçu
    • Utilisez un SurfaceView (compatibilité pré-Android-4.0) ou SurfaceTexture (Android 4+, peut être rendu transparent)
    • Réglez-le et initialisez-le avant de prendre la photo
    • Attendez que le SurfaceViewSurfaceHolder (via getHolder()) signale surfaceCreated() ou le TextureView signale onSurfaceTextureAvailable à son SurfaceTextureListener avant de définir et d'initialiser l'aperçu.
  • Assurez-vous que l'aperçu est visible:
    • Ajoutez-le au WindowManager
    • Assurez-vous que sa taille de mise en page est d'au moins 1 x 1 pixels (vous pouvez commencer par le faire MATCH_PARENT X MATCH_PARENT pour tester)
    • Assurez-vous que sa visibilité est View.VISIBLE (qui semble être la valeur par défaut si vous ne le spécifiez pas)
    • Assurez-vous d'utiliser le FLAG_HARDWARE_ACCELERATED dans le LayoutParams s'il s'agit d'un TextureView.
  • Utilisez le rappel JPEG de takePicture car la documentation indique que les autres rappels ne sont pas pris en charge sur tous les appareils

Dépannage

  • Si surfaceCreated/onSurfaceTextureAvailable n'est pas appelé, le SurfaceView/TextureView n'est probablement pas affiché.
  • Si takePicture échoue, assurez-vous d'abord que l'aperçu fonctionne correctement. Vous pouvez supprimer votre appel takePicture et laisser l'aperçu s'exécuter pour voir s'il s'affiche à l'écran.
  • Si l'image est plus sombre qu'elle ne devrait l'être, vous devrez peut-être attendre environ une seconde avant d'appeler takePicture afin que l'appareil photo ait le temps d'ajuster son exposition une fois l'aperçu lancé.

Masquer l'aperçu

  • Faites l'aperçu View Taille 1x1 pour minimiser sa visibilité ( ou essayez 8x16 pour éventuellement plus de fiabilité )

    new WindowManager.LayoutParams(1, 1, /*...*/)
    
  • Déplacez l'aperçu hors du centre pour réduire sa visibilité:

    new WindowManager.LayoutParams(width, height,
        Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/)
    
  • Rendre l'aperçu transparent (ne fonctionne que pour TextureView)

    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
        width, height, /*...*/
        PixelFormat.TRANSPARENT);
    params.alpha = 0;
    

Exemple de travail (testé sur Sony Xperia M, Android 4.3)

/** Takes a single photo on service start. */
public class PhotoTakingService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        takePhoto(this);
    }

    @SuppressWarnings("deprecation")
    private static void takePhoto(final Context context) {
        final SurfaceView preview = new SurfaceView(context);
        SurfaceHolder holder = preview.getHolder();
        // deprecated setting, but required on Android versions prior to 3.0
        holder.setType(SurfaceHolder.SURFACE_TYPE_Push_BUFFERS);

        holder.addCallback(new Callback() {
            @Override
            //The preview must happen at or after this point or takePicture fails
            public void surfaceCreated(SurfaceHolder holder) {
                showMessage("Surface created");

                Camera camera = null;

                try {
                    camera = Camera.open();
                    showMessage("Opened camera");

                    try {
                        camera.setPreviewDisplay(holder);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }

                    camera.startPreview();
                    showMessage("Started preview");

                    camera.takePicture(null, null, new PictureCallback() {

                        @Override
                        public void onPictureTaken(byte[] data, Camera camera) {
                            showMessage("Took picture");
                            camera.release();
                        }
                    });
                } catch (Exception e) {
                    if (camera != null)
                        camera.release();
                    throw new RuntimeException(e);
                }
            }

            @Override public void surfaceDestroyed(SurfaceHolder holder) {}
            @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
        });

        WindowManager wm = (WindowManager)context
            .getSystemService(Context.WINDOW_SERVICE);
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                1, 1, //Must be at least 1x1
                WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
                0,
                //Don't know if this is a safe default
                PixelFormat.UNKNOWN);

        //Don't set the preview visibility to GONE or INVISIBLE
        wm.addView(preview, params);
    }

    private static void showMessage(String message) {
        Log.i("Camera", message);
    }

    @Override public IBinder onBind(Intent intent) { return null; }
}
34
Sam

Sur Android 4.0 et supérieur (niveau API> = 14), vous pouvez utiliser TextureView pour prévisualiser le flux de la caméra et le rendre invisible afin de ne pas le montrer au utilisateur. Voici comment:

Créez d'abord une classe pour implémenter un SurfaceTextureListener qui obtiendra les rappels de création/mise à jour pour la surface d'aperçu. Cette classe prend également un objet caméra en entrée, afin qu'il puisse appeler la fonction startPreview de la caméra dès que la surface est créée:

public class CamPreview extends TextureView implements SurfaceTextureListener {

  private Camera mCamera;

  public CamPreview(Context context, Camera camera) {
    super(context);
    mCamera = camera;
   }

  @Override
  public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
    Camera.Size previewSize = mCamera.getParameters().getPreviewSize();
    setLayoutParams(new FrameLayout.LayoutParams(
        previewSize.width, previewSize.height, Gravity.CENTER));

    try{
      mCamera.setPreviewTexture(surface);
     } catch (IOException t) {}

    mCamera.startPreview();
    this.setVisibility(INVISIBLE); // Make the surface invisible as soon as it is created
  }

  @Override
  public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
      // Put code here to handle texture size change if you want to
  }

  @Override
  public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    return true;
  }

  @Override
  public void onSurfaceTextureUpdated(SurfaceTexture surface) {
      // Update your view here!
  }
}

Vous devrez également implémenter une classe de rappel pour traiter les données d'aperçu:

public class CamCallback implements Camera.PreviewCallback{
  public void onPreviewFrame(byte[] data, Camera camera){
     // Process the camera data here
  }
}

Utilisez les classes CamPreview et CamCallback ci-dessus pour configurer la caméra dans la fonction de démarrage onCreate () ou similaire de votre activité:

// Setup the camera and the preview object
Camera mCamera = Camera.open(0);
CamPreview camPreview = new CamPreview(Context,mCamera);
camPreview.setSurfaceTextureListener(camPreview);

// Connect the preview object to a FrameLayout in your UI
// You'll have to create a FrameLayout object in your UI to place this preview in
FrameLayout preview = (FrameLayout) findViewById(R.id.cameraView); 
preview.addView(camPreview);

// Attach a callback for preview
CamCallback camCallback = new CamCallback();
mCamera.setPreviewCallback(camCallback);
21
Varun Gulshan

Il existe un moyen de le faire, mais c'est un peu délicat. ce qui devrait être fait, c'est d'attacher un support de surface au gestionnaire de fenêtres du service

WindowManager wm = (WindowManager) mCtx.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
            WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
            PixelFormat.TRANSLUCENT);        
wm.addView(surfaceview, params);

puis définissez

surfaceview.setZOrderOnTop(true);
mHolder.setFormat(PixelFormat.TRANSPARENT);

où mHolder est le support que vous obtenez de la vue de surface.

de cette façon, vous pouvez jouer avec l'alpha de la vue de surface, le rendre complètement transparent, mais la caméra obtiendra toujours des images.

c'est comme ça que je le fais. J'espère que ça aide :)

20
Vlad

Nous avons résolu ce problème en utilisant un SurfaceView factice (non ajouté à l'interface graphique réelle) dans les versions inférieures à 3.0 (ou disons que 4.0, car un service de caméra sur une tablette n'a pas vraiment de sens). Dans les versions> = 4.0, cela ne fonctionnait que dans l'émulateur; (L'utilisation de SurfaceTexture (et setSurfaceTexture ()) au lieu de SurfaceView (et setSurfaceView ()) fonctionnait ici. Au moins, cela fonctionne sur Nexus S.

Je pense que c'est vraiment une lacune du framework Android.

13
mnl

Dans le "Exemple de travail de Sam" (Merci Sam ...)

if at istruction "wm.addView (preview, params);"

obtenir l'exception "Impossible d'ajouter la fenêtre Android.view.ViewRoot - autorisation refusée pour ce type de fenêtre"

résoudre en utilisant cette autorisation dans AndroidManifest:

<uses-permission Android:name="Android.permission.SYSTEM_ALERT_WINDOW"/>
3
Giulio

Vous pouvez essayer ce code de travail, ce service cliquez sur l'image avant, si vous voulez capturer l'image de la caméra arrière, puis décommentez backCamera dans le code et commentez frontCamera.

Remarque: - Autorisez la caméra et le stockage sur l'application et démarrez le service depuis l'activité ou n'importe où.

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        CapturePhoto();
    }

    private void CapturePhoto() {

        Log.d("kkkk","Preparing to take photo");
        Camera camera = null;

        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();

            int frontCamera = 1;
            //int backCamera=0;

            Camera.getCameraInfo(frontCamera, cameraInfo);

            try {
                camera = Camera.open(frontCamera);
            } catch (RuntimeException e) {
                Log.d("kkkk","Camera not available: " + 1);
                camera = null;
                //e.printStackTrace();
            }
            try {
                if (null == camera) {
                    Log.d("kkkk","Could not get camera instance");
                } else {
                    Log.d("kkkk","Got the camera, creating the dummy surface texture");
                     try {
                         camera.setPreviewTexture(new SurfaceTexture(0));
                        camera.startPreview();
                    } catch (Exception e) {
                        Log.d("kkkk","Could not set the surface preview texture");
                        e.printStackTrace();
                    }
                    camera.takePicture(null, null, new Camera.PictureCallback() {

                        @Override
                        public void onPictureTaken(byte[] data, Camera camera) {
                            File pictureFileDir=new File("/sdcard/CaptureByService");

                            if (!pictureFileDir.exists() && !pictureFileDir.mkdirs()) {
                                pictureFileDir.mkdirs();
                            }
                            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyymmddhhmmss");
                            String date = dateFormat.format(new Date());
                            String photoFile = "ServiceClickedPic_" + "_" + date + ".jpg";
                            String filename = pictureFileDir.getPath() + File.separator + photoFile;
                            File mainPicture = new File(filename);

                            try {
                                FileOutputStream fos = new FileOutputStream(mainPicture);
                                fos.write(data);
                                fos.close();
                                Log.d("kkkk","image saved");
                            } catch (Exception error) {
                                Log.d("kkkk","Image could not be saved");
                            }
                            camera.release();
                        }
                    });
                }
            } catch (Exception e) {
                camera.release();
            }
    }
}
1
kdblue