Tomar fotos de la cámara sin vista previa

Estoy escribiendo una aplicación para Android 1.5 que comienza justo después del arranque. Este es un Service y debe tomar una foto sin vista previa. Esta aplicación registrará la densidad de luz en algunas áreas. Pude tomar una foto pero la foto era negra.

Después de investigar durante mucho tiempo, me encontré con un hilo de error al respecto. Si no genera una vista previa, la imagen será negra ya que la cámara Android necesita una vista previa para configurar la exposición y el enfoque. SurfaceView un SurfaceView y el oyente, pero el evento onSurfaceCreated() nunca se dispara.

Supongo que la razón es que la superficie no se está creando visualmente. También he visto algunos ejemplos de llamadas a la cámara estáticamente con MediaStore.CAPTURE_OR_SOMETHING que toma una imagen y la guarda en la carpeta deseada con dos líneas de código, pero no toma una foto también.

¿Debo usar IPC y bindService() para llamar a esta función? ¿O hay un método alternativo para lograr esto?

es realmente extraño que la cámara en la plataforma Android no pueda transmitir video hasta que tenga una superficie de vista previa válida. parece que los arquitectos de la plataforma no pensaban en aplicaciones de transmisión de video de terceros. incluso en el caso de la realidad aumentada, la imagen puede presentarse como una especie de sustitución visual, no como una transmisión de cámara en tiempo real.

de todos modos, simplemente puede cambiar el tamaño de la superficie de vista previa a 1×1 píxeles y colocarla en algún lugar en la esquina del widget (elemento visual). por favor, preste atención: cambie el tamaño de la superficie de vista previa, no del tamaño del marco de la cámara.

por supuesto, este truco no elimina la transmisión de datos no deseados (para la vista previa) que consume algunos recursos del sistema y la batería.

Encontré la respuesta a esto en los documentos de la cámara de Android .

Nota: Es posible usar MediaRecorder sin crear primero una vista previa de la cámara y omitir los primeros pasos de este proceso. Sin embargo, dado que los usuarios normalmente prefieren ver una vista previa antes de comenzar una grabación, ese proceso no se trata aquí.

Puede encontrar las instrucciones paso a paso en el enlace de arriba. Después de las instrucciones, indicará la cita que he proporcionado anteriormente.

En realidad, es posible, pero debe falsificar la vista previa con un SurfaceView ficticio

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

Más detalles encontrados en mi blog .

Actualización 21/09/11: Aparentemente esto no funciona para todos los dispositivos Android.

Tomando la foto

Haga que esto funcione primero antes de tratar de ocultar la vista previa.

  • Configurar correctamente la vista previa
    • Utilice SurfaceView ( SurfaceView previa a Android-4.0) o SurfaceTexture (Android 4+, puede hacerse transparente)
    • Establecer e inicializar antes de tomar la foto
    • Espere a que SurfaceView de SurfaceHolder (a través de getHolder() ) informe surfaceCreated() o TextureView para informar sobre onSurfaceTextureAvailable a SurfaceTextureListener antes de configurar e inicializar la vista previa.
  • Asegúrese de que la vista previa esté visible:
    • WindowManager a WindowManager
    • Asegúrese de que su tamaño de diseño sea de al menos 1×1 píxeles (es posible que desee comenzar haciéndolo MATCH_PARENT x MATCH_PARENT para la prueba)
    • Asegúrese de que su visibilidad sea View.VISIBLE (que parece ser la predeterminada si no la especifica)
    • Asegúrese de utilizar FLAG_HARDWARE_ACCELERATED en los LayoutParams si se trata de un TextureView .
  • Utilice la callback JPEG de takePicture ya que la documentación dice que las otras devoluciones de llamada no son compatibles con todos los dispositivos

Solución de problemas

  • Si no se llama a SurfaceView / TextureView , SurfaceView / TextureView probablemente no se muestra.
  • Si takePicture falla, primero asegúrese de que la vista previa funcione correctamente. Puede eliminar su llamada de takePicture y dejar que se ejecute la vista previa para ver si se muestra en la pantalla.
  • Si la imagen es más oscura de lo que debería ser, es posible que deba demorar aproximadamente un segundo antes de llamar a takePicture para que la cámara tenga tiempo de ajustar su exposición una vez que haya comenzado la vista previa.

Ocultar la vista previa

  • Haga la vista previa View tamaño 1×1 para minimizar su visibilidad ( o intente 8×16 para posiblemente más confiabilidad )

     new WindowManager.LayoutParams(1, 1, /*...*/) 
  • Mueva la vista previa fuera del centro para reducir su notoriedad:

     new WindowManager.LayoutParams(width, height, Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/) 
  • Haga que la vista previa sea transparente (solo funciona para TextureView )

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

Ejemplo de trabajo (probado en 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; } } 

Hay una manera de hacerlo, pero es un tanto complicado. lo que debe hacerse, es conectar un soporte de superficie al administrador de ventanas del servicio

 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); 

y luego establecer

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

donde mHolder es el titular que obtienes de la vista de la superficie.

de esta forma, puede jugar con el alfa de la superficie, hacerlo completamente transparente, pero la cámara aún obtendrá marcos.

así es como lo hago. Espero eso ayude 🙂

En Android 4.0 y versiones posteriores (nivel de API> = 14), puede usar TextureView para obtener una vista previa de la transmisión de la cámara y hacerla invisible para no mostrarla al usuario. Así es cómo:

Primero crea una clase para implementar SurfaceTextureListener que obtendrá las devoluciones de llamada de creación / actualización para la superficie de vista previa. Esta clase también toma un objeto de cámara como entrada, de modo que puede llamar a la función startPreview de la cámara tan pronto como se crea la superficie:

 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! } } 

También necesitarás implementar una clase de callback para procesar los datos de vista previa:

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

Utilice las clases anteriores de CamPreview y CamCallback para configurar la cámara en la función de inicio onCreate () de su actividad o similar:

 // 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); 

Solucionamos este problema al usar un SurfaceView ficticio (no agregado a la GUI real) en las versiones por debajo de 3.0 (o digamos 4.0 como un servicio de cámara en una tableta realmente no tiene sentido). En versiones> = 4.0 esto funcionó solo en el emulador; (el uso de SurfaceTexture (y setSurfaceTexture ()) en lugar de SurfaceView (y setSurfaceView ()) funcionó aquí. Al menos esto funciona en Nexus S.

Creo que esto realmente es una deficiencia del marco Android.

En el “Ejemplo de trabajo de Sam” (Gracias, Sam …)

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

obtener la excepción “No se puede agregar la ventana android.view.ViewRoot – permiso denegado para este tipo de ventana”

resolver mediante el uso de este permiso en AndroidManifest:

  

Puede probar este código de trabajo, este servicio haga clic en la imagen frontal, si desea capturar la imagen de la cámara trasera, descomente la cámara inversa en el código y comente frontCamera.

Nota: – Permitir el permiso de la cámara y el almacenamiento a la aplicación e iniciar el servicio desde la actividad o desde cualquier lugar.

 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(); } } }