58

I am writing an Android 1.5 application which starts just after boot-up. This is a Service and should take a picture without preview. This app will log the light density in some areas whatever. I was able to take a picture but the picture was black.

After researching for a long time, I came across a bug thread about it. If you don't generate a preview, the image will be black since Android camera needs preview to setup exposure and focus. I've created a SurfaceView and the listener, but the onSurfaceCreated() event never gets fired.

I guess the reason is, the surface is not being created visually. I've also seen some examples of calling the camera statically with MediaStore.CAPTURE_OR_SOMETHING which takes a picture and saves in the desired folder with two lines of code, but it doesn't take a picture too.

Do I need to use IPC and bindService() to call this function? Or is there an alternative method to achieve this?

eyurdakul
  • 894
  • 2
  • 12
  • 29
  • See also _[Take Picture without preview android](http://stackoverflow.com/questions/10799976/take-picture-without-preview-android)_ – Alex Cohn Oct 09 '14 at 19:32
  • https://github.com/kevalpatel2106/android-hidden-camera - Checkout this library, that provides background camera. – Keval Patel Dec 09 '16 at 16:23

9 Answers9

53

it is really weird that camera on android platform can't stream video until it given valid preview surface. it seems that the architects of the platform was not thinking about 3rd party video streaming applications at all. even for augmented reality case the picture can be presented as some kind of visual substitution, not real time camera stream.

anyway, you can simply resize preview surface to 1x1 pixels and put it somewhere in the corner of the widget (visual element). please pay attention - resize preview surface, not camera frame size.

of course such trick does not eliminate unwanted data streaming (for preview) which consumes some system resources and battery.

  • 9
    There is one catch with 1×1 surface: it may be slow on some devices (e.g. Samsung) which fail to run the HW image converter when the target size does not divide 4 (or maybe 8) – Alex Cohn Dec 22 '12 at 16:26
  • Also on some devices it just does not work, throws RuntimeException (old Nexus One at least as far as I know) – comodoro Sep 01 '13 at 16:45
  • 2
    I suspect Google intentionally made it, or otherwise I cannot imagine their artchitects plan this way. – user1914692 Oct 04 '13 at 23:22
  • Does not work if you have a service only (e.g. wanna use the camera when the app is in the background) – mnl Jan 13 '14 at 12:01
  • @mnl, it worked for me in a service. I added the preview to the window manager as a system overlay. – Sam Nov 22 '14 at 21:51
  • @comodoro, what exactly caused it to fail? Was the issue that the Nexus One doesn't support resizing the preview at all? Or did it just not support certain sizes? Did you change the size of the `SurfaceHolder` or the `SurfaceView`? What code did you use to change it? – Sam Nov 22 '14 at 21:58
39

I found the answer to this in the Android Camera Docs.

Note: It is possible to use MediaRecorder without creating a camera preview first and skip the first few steps of this process. However, since users typically prefer to see a preview before starting a recording, that process is not discussed here.

You can find the step by step instructions at the link above. After the instructions, it will state the quote that I have provided above.

Andrew T.
  • 4,701
  • 8
  • 43
  • 62
Phillip Scott Givens
  • 5,256
  • 4
  • 32
  • 54
  • 1
    Did you follow the step by step instructions? I know that this is the lowest rated answer (disappointing), but I followed the documentation and it works fine for me. – Phillip Scott Givens Nov 09 '12 at 22:35
  • @phillip-scott-givens: yes it works, but not for picture capture, which is the question subj. You can only skip the preview setup if you use MediaRecorder. – Alex Cohn Dec 22 '12 at 16:10
  • 6
    Uhg, I am STILL losing reputation points on this. HOW DO I DELETE THIS POST? If you RTFM, IT WORKS! I swear it. It works for video. It works for pictures. I posted this because I found it and it works. Now, a year later, I am still being down-voted. I tried the "delete" link and it just asks me if I want to "vote to delete" this post. Please do not down-vote me anymore. Instead, please respond here and tell me how to delete this thing. – Phillip Scott Givens Jan 19 '13 at 22:12
  • basically you can't, but you can flag it for moderator attention: http://meta.stackexchange.com/questions/25088/how-can-i-delete-my-post-on-stack-overflow – apanloco Jan 25 '13 at 08:08
  • 1
    I find this answer helpful - in my application it appears to be better to take a short video instead of a picture. – NumberFour Oct 18 '13 at 15:54
  • 5
    this is really interesting, thanks a lot. Will try this. Don't worry about the down votes! –  Dec 11 '13 at 19:50
  • Can you help please, how can I capture a video without preview? I already asked the question here http://stackoverflow.com/questions/33196518/capture-video-without-surface-using-media-recorder but haven't answer yet... thanks! – AsfK Oct 26 '15 at 07:12
37

Actually it is possible, but you have to fake the preview with a dummy SurfaceView

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

Update 9/21/11: Apparently this does not work for every Android device.

Chase Roberts
  • 9,082
  • 13
  • 73
  • 131
Frank
  • 1,426
  • 1
  • 14
  • 14
  • 8
    Use a SurfaceTexture and setSurfaceTexture above 4.0 – HannahMitt Mar 16 '13 at 01:14
  • 2
    To make it work on every device, surface must be added somewhere and actually created, best is to use the holder's callbacks. Callbacks are only called if view is visible and not sized 0x0. setAlpha(0) seems to be ok, but only available on API 11 and above. – 3c71 Aug 17 '13 at 07:56
  • Just to confirm.. Does not work on Galaxy Nexus either RuntimeException: takePicture failed – Jakob Harteg Jul 13 '14 at 11:55
  • @3c71, using a transparent `PixelFormat` and `setAlpha(0)` didn't make it transparent on my Sony Xperia M running Android 4.3. The preview was still opaque. – Sam Nov 22 '14 at 22:01
  • @3c71, actually `setAlpha(0)` did work after using `setFormat(PixelFormat.TRANSPARENT)` on the `SurfaceHolder` and using the same `PixelFormat` in the `SurfaceView` constructor. – Sam Nov 22 '14 at 22:13
  • This is returning black bitmap! – Muhammad Babar Dec 04 '14 at 11:41
36

Taking the Photo

Get this working first before trying to hide the preview.

  • Correctly set up the preview
    • Use a SurfaceView (pre-Android-4.0 compatibility) or SurfaceTexture (Android 4+, can be made transparent)
    • Set and initialise it before taking the photo
    • Wait for the SurfaceView's SurfaceHolder (via getHolder()) to report surfaceCreated() or the TextureView to report onSurfaceTextureAvailable to its SurfaceTextureListener before setting and initialising the preview.
  • Ensure the preview is visible:
    • Add it to the WindowManager
    • Ensure its layout size is at least 1x1 pixels (you might want to start by making it MATCH_PARENT x MATCH_PARENT for testing)
    • Ensure its visibility is View.VISIBLE (which seems to be the default if you don't specify it)
    • Ensure you use the FLAG_HARDWARE_ACCELERATED in the LayoutParams if it's a TextureView.
  • Use takePicture's JPEG callback since the documentation says the other callbacks aren't supported on all devices

Troubleshooting

  • If surfaceCreated/onSurfaceTextureAvailable doesn't get called, the SurfaceView/TextureView probably isn't being displayed.
  • If takePicture fails, first ensure the preview is working correctly. You can remove your takePicture call and let the preview run to see if it displays on the screen.
  • If the picture is darker than it should be, you might need to delay for about a second before calling takePicture so that the camera has time to adjust its exposure once the preview has started.

Hiding the Preview

  • Make the preview View 1x1 size to minimise its visibility (or try 8x16 for possibly more reliability)

    new WindowManager.LayoutParams(1, 1, /*...*/)
    
  • Move the preview out of the centre to reduce its noticeability:

    new WindowManager.LayoutParams(width, height,
        Integer.MIN_VALUE, Integer.MIN_VALUE, /*...*/)
    
  • Make the preview transparent (only works for TextureView)

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

Working Example (tested on 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; }
}
Sam
  • 40,644
  • 36
  • 176
  • 219
  • This is finally an elaborate answer on this topic with a working example! Thank you – NumberFour Dec 24 '14 at 21:17
  • This solution no longer works on API level >=23. `Cannot add previewandroid.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@664dc37 -- permission denied for window type 2006` – Ruchir Baronia Jul 07 '19 at 23:47
  • @RuchirBaronia, here are a couple of options: 1. Use [the technique used by the ML Kit team](https://github.com/firebase/quickstart-android/blob/de662b77245cd04cb93fce386f1c98de5a44bb8e/mlkit/app/src/main/java/com/google/firebase/samples/apps/mlkit/common/CameraSource.java) 2. Switch to the `camera2` API – Sam Jul 08 '19 at 08:02
22

On Android 4.0 and above (API level >= 14), you can use TextureView to preview the camera stream and make it invisible so as to not show it to the user. Here's how:

First create a class to implement a SurfaceTextureListener that will get the create/update callbacks for the preview surface. This class also takes a camera object as input, so that it can call the camera's startPreview function as soon as the surface is created:

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

You'll also need to implement a callback class to process the preview data:

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

Use the above CamPreview and CamCallback classes to setup the camera in your activity's onCreate() or similar startup function:

// 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);
Varun Gulshan
  • 507
  • 4
  • 7
  • 3
    The OP states that he is going to use this in the Service. Im also looking for a way how to take picture from a Service. By placing the `FrameLayout` you mean placing it in the Activity which starts the Service? What if the Service calls this from background? Does the Activity with this `FrameLayout` attached pop up? – NumberFour Oct 18 '13 at 14:17
  • @NumberFour: the third piece of code is not relevant for your case. – Alex Cohn Nov 12 '13 at 20:36
  • 2
    How could I use this code in a service ?? can I call CamPreview from service ?? – someone Jul 17 '14 at 19:22
  • worked for me with minor changes in the Callback Interface – shashi2459 Feb 08 '16 at 11:31
20

There is a way of doing this but it's somewhat tricky. what should be done, is attach a surfaceholder to the window manager from the 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);

and then set

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

where mHolder is the holder you get from the surface view.

this way, you can play with the surfaceview's alpha, make it completly transparent, but the camera will still get frames.

that's how i do it. hope it helps :)

Vlad
  • 735
  • 2
  • 10
  • 19
  • Can you please show me little bit more source code? It didn't work for me :( You can also contact me per email. – Valelik Dec 05 '12 at 18:37
  • I get a permission denied exception for wm.addView(surfaceView, params); – Sathesh Dec 30 '12 at 19:50
  • How did you over come that? Can you please help? – Sathesh Dec 30 '12 at 19:50
  • i didn't encounter this exception :\ – Vlad Dec 30 '12 at 20:41
  • 5
    I have added this permission and the exception is gone. – Sathesh Dec 31 '12 at 18:41
  • 1
    Oh, I've had this permission to begin with.. so that's why i didn't encounter the exception :) – Vlad Jan 03 '13 at 13:58
  • I would also like to see some more source code please, Im new to the camera API. Do I get the `mHolder` from surfaceView.getHolder()? How do I create the SurfaceView? surfaceView = new SufaceView(this) would work? Thanks. – NumberFour Aug 16 '13 at 16:30
  • 2
    So this is what happened when I tried this solution and exited my application: http://i.imgur.com/g8Fmnj6.png – BVB Oct 03 '13 at 21:33
  • great post @Vlad thx! Can you please tell me how to make preview transparent? I'm trying for about an hour and no success so far. – sswierczek Oct 29 '13 at 21:01
  • Has anyone gotten this to work on a Nexus 5? I've had success on about 5 other devices but I can't get it working on the Nexus 5. – Matt R Dec 13 '13 at 23:18
  • 3
    @BVB The window manager, doesn't automatically remove views that you've added.. so save a reference to the surfaceview and when exiting add wm.removeView(surfaceview) – Vlad Dec 18 '13 at 12:08
  • Thanks for the `WindowManager` tip. The solution to set the surface view as transparent didn't work for me. Any other ideas on how to do that ? – Muzikant Apr 27 '14 at 15:46
  • Is it actually guaranteed that the camera preview will support a transparent format? [From what I've read](http://developer.android.com/reference/android/hardware/Camera.Parameters.html#getSupportedPreviewFormats()), the only format guaranteed to work across all phones is `ImageFormat.NV21`. – Sam Nov 22 '14 at 21:49
13

We solved this problem by using a dummy SurfaceView (not added to actual GUI) in versions below 3.0 (or let's say 4.0 as a camera service on a tablet does not really make sense). In versions >= 4.0 this worked in the emulator only ;( The use of SurfaceTexture (and setSurfaceTexture()) instead of SurfaceView (and setSurfaceView()) worked here. At least this works on Nexus S.

I think this really is a shortcoming of the Android framework.

mnl
  • 411
  • 5
  • 16
3

In the "Working Example by Sam" (Thank you Sam... )

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

obtain exception "Unable to add window android.view.ViewRoot -- permission denied for this window type"

resolve by using this permission in AndroidManifest:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
Giulio
  • 33
  • 3
1

You can try this working code, This service click front picture, if you want to capture back camera picture then uncomment back camera in code and comment front camera.

Note :- Allow Camera and Storage permission to App And startService from Activity or anywhere.

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();
            }
    }
}
Pratik Butani
  • 60,504
  • 58
  • 273
  • 437
iamkdblue
  • 3,448
  • 2
  • 25
  • 43