cintron
cintron

Reputation: 523

OnPictureTaken() Timing/Saving Issue

I'm creating a custom camera application (which'll be contained within a larger, main application) that loads up a view with the camera -preview and a picture gallery preview - so the user can either take a picture or select a preexisting one. If the user does take a photo, I just wanna display a confirmation dialog and, if confirmed, pass control back to the main activity. I can't store the photo: need to quickly use it as an attachment, and once sent via the main activity, dispose of it.

The problem comes up when I take a picture and onPictureTaken() is called: it either doesn't work, or takes an absurdly long time for it to save a photo. Also, I can't seem to figure out how to extract the photo right on the spot, without saving it to a directory; I planned on immediately deleting the directory, but there's got to be a cleaner way (essentially, I just wanna access that raw byte array, without having to save it).

I want it to be modular (gonna use the camera a lot in my upcoming projects), and I think that's what's adding to the overall lag.

I followed Google's Developer Android Guide on Cameras, and most of my code is derived from the examples presented there.

I'm also using ActionBarSherlock.

I currently have three classes:

CameraManager

public class CameraManager {

private static Camera CAMERA;

    /** Check if this device has a camera */
    public static boolean  doesDeviceHaveCamera(Context context) {
        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
            return true;
        } 
        else {
            return false;
        }
    }

    public static Camera getCameraInstance(){
        CAMERA = null;
        try {
            CAMERA = Camera.open();
        }
        catch (Exception e){
            // Camera is not available (in use or does not exist)
        }
        return CAMERA;
    }
}

CameraPreview

    public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private static final String TAG = "CameraPreview";
    private SurfaceHolder surfaceHolder;
    private Camera camera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        this.camera = camera;
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // deprecated setting, but required on Android versions prior to 3.0
    }

    public void surfaceCreated(SurfaceHolder holder) {
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } 
        catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        camera.stopPreview();
        camera.release();
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.

        if (surfaceHolder.getSurface() == null){
            return;
        }
        try {
            camera.stopPreview();
        } 
        catch (Exception e){
            // ignore: tried to stop a non-existent preview
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here
        camera.setDisplayOrientation(90);
        // start preview with new settings
        try {
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
        } 
        catch (Exception e){
            Log.d(TAG, "Error starting camera preview: " + e.getMessage());
        }
    }
}

CameraActivity

public class CameraActivity extends SherlockActivity {

    private static final String TAG = "CameraActivity";
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    private Camera camera;
    private CameraPreview preview;
    private PictureCallback picture = new PictureCallback() { 
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
            if (pictureFile == null){
                Log.d(TAG, "Error creating media file, check storage permissions: media file is null.");
                return;
            }
            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } 
            catch (FileNotFoundException e) {
                Log.d(TAG, "File not found: " + e.getMessage());
            } 
            catch (IOException e) {
                Log.d(TAG, "Error accessing file: " + e.getMessage());
            }
        }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportActionBar().hide();
        setContentView(R.layout.camera_view);

        /**Get a camera instance**/
        if(CameraManager.doesDeviceHaveCamera(this)){
            camera = CameraManager.getCameraInstance();
        }
        else{
            return;
        }

        /**Create a SurfaceView preview (holds the camera feed) and set it to the corresponding XML layout**/
        preview = new CameraPreview(this, camera);
        FrameLayout previewContainer = (FrameLayout) findViewById(R.id.camera_preview);
        previewContainer.addView(preview);

        /**Set up a capture button, which takes the picture**/
        Button captureButton = (Button) findViewById(R.id.button_capture);
        captureButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                camera.takePicture(null, null, picture);
            }
        });
    }

    /** Create a File for saving an image or video */
    private static File getOutputMediaFile(int type){
        // To be safe, you should check that the SDCard is mounted
        // using Environment.getExternalStorageState() before doing this.

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyDirectory");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyDirectory", "failed to create directory");
                return null;
            }
        }
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
        File mediaFile;
        if (type == MEDIA_TYPE_IMAGE){
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                    "IMG_"+ timeStamp + ".jpg");
        } 
        else if(type == MEDIA_TYPE_VIDEO) {
            mediaFile = new File(mediaStorageDir.getPath() + File.separator +
                    "VID_"+ timeStamp + ".mp4");
        } 
        else {
            return null;
        }
        return mediaFile;
    }
}

I'm calling CameraActivity from a button on my main activity screen.

Upvotes: 1

Views: 1533

Answers (2)

Alex Cohn
Alex Cohn

Reputation: 57203

If you open the camera on a secondary event thread, your pictureTaken() callback will be called on that thread, not hindering UI responsiveness. Note that you can still call camera.takePicture() from onClick(), i.e. from the UI thread.

This still is not a silver bullet: you may want to offload writing file to a yet another working thread, to let the camera take the next photo. And as Mark wrote above, don't write to file if you only need live access to the raw byte array.

Upvotes: 1

CommonsWare
CommonsWare

Reputation: 1007369

The problem comes up when I take a picture and onPictureTaken() is called: it either doesn't work, or takes an absurdly long time for it to save a photo

It is a large file, one that you are writing to on the main application thread.

essentially, I just wanna access that raw byte array, without having to save it

Then don't save it. Carefully use a static data member to put the byte array somewhere that the main activity can use (and, when you are done with it, null out the static reference, so the memory can be garbage-collected).

Upvotes: 2

Related Questions