net.microtrash.clapcamera.Preview.java Source code

Java tutorial

Introduction

Here is the source code for net.microtrash.clapcamera.Preview.java

Source

package net.microtrash.clapcamera;

/*******************************************************************************
 * ClapCamera
 *
 * Copyright 2013 by Stephan Petzl
 * http://www.stephanpetzl.com
 *
 * This application is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *******************************************************************************/

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.ImageFormat;

import android.hardware.Camera;
import android.hardware.Camera.ErrorCallback;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.Size;
import android.os.Build;
import android.os.Debug;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;

import com.crittercism.app.Crittercism;

/**
 * 
 * openCamera() onSizeChanged() onLayout() onLayout() surfaceCreated()
 * surfaceChanged() onLayout()
 */
class Preview extends ViewGroup implements SurfaceHolder.Callback { // <1>
    private static final String TAG = "Preview";

    SurfaceHolder mHolder; // <2>
    public Camera camera; // <3>
    SurfaceView mSurfaceView;

    int l2 = 0, t2 = 0, r2 = 0, b2 = 0;
    int padding = 10;
    PreviewCallback cb;
    private double downscalingFactor = 1;
    // the size of this view. gets set in onMeasure()
    int fullWidth, fullHeight;
    Size bestPictureSize = null;
    Size bestPreviewSize = null;

    private String allResolutions;

    private Context context;

    public Preview(Context context, PreviewCallback callback) {
        super(context);
        this.cb = callback;
        init(context);
    }

    private void init(Context context) {
        setKeepScreenOn(true);
        this.context = context;
        mSurfaceView = new SurfaceView(context);
        addView(mSurfaceView);
        mHolder = mSurfaceView.getHolder(); // <4>
        mHolder.addCallback(this); // <5>
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); // <6>
    }

    static public void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height) {
        final int frameSize = width * height;

        for (int j = 0, yp = 0; j < height; j++) {
            int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
            for (int i = 0; i < width; i++, yp++) {
                int y = (0xff & ((int) yuv420sp[yp])) - 16;
                if (y < 0)
                    y = 0;
                if ((i & 1) == 0) {
                    v = (0xff & yuv420sp[uvp++]) - 128;
                    u = (0xff & yuv420sp[uvp++]) - 128;
                }
                int y1192 = 1192 * y;
                int r = (y1192 + 1634 * v);
                int g = (y1192 - 833 * v - 400 * u);
                int b = (y1192 + 2066 * u);

                if (r < 0)
                    r = 0;
                else if (r > 262143)
                    r = 262143;
                if (g < 0)
                    g = 0;
                else if (g > 262143)
                    g = 262143;
                if (b < 0)
                    b = 0;
                else if (b > 262143)
                    b = 262143;

                rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
            }
        }
    }

    private Bitmap previewBitmap;

    /*not needed at the moment
     Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
       private int imageFormat;
           
        
       public void onPreviewFrame(byte[] data, Camera camera) {
     Parameters parameters = camera.getParameters();
     imageFormat = parameters.getPreviewFormat();
     if (imageFormat == ImageFormat.NV21) {
            
        int[] argb8888 = new int[Preview.this.getPreviewWidth()*Preview.this.getPreviewHeight()];
        Preview.decodeYUV420SP(argb8888, data, Preview.this.getPreviewWidth(), Preview.this.getPreviewHeight());
        
            
        previewBitmap = Bitmap.createBitmap(argb8888, Preview.this.getPreviewWidth(), Preview.this.getPreviewHeight(), Config.ARGB_8888);
        cb.previewBitmapAvailable(previewBitmap);
     }
        
       }
        
    };*/

    public void openCamera() {
        Log.d(TAG, "openCamera()");
        if (this.camera == null) {
            try {
                this.camera = Camera.open();
                this.camera.setErrorCallback(new ErrorCallback() {
                    @Override
                    public void onError(int error, Camera camera) {
                        Log.e(TAG, "error! code:" + error);
                        Toast.makeText(context, "Camera error occured: " + error, 8000).show();
                    }
                });

                requestLayout(); // -> onSizeChanged() -> onLayout()
            } catch (Exception e) {
                String errorMessage = "Manufacturer: " + Build.MANUFACTURER + "; Model:" + Build.MODEL
                        + "; Camera: " + this.camera + "; Stacktrace:" + Tools.exception2String(e);
                Log.d(TAG, "error occured: " + errorMessage);

                Toast.makeText(context,
                        "Uuups, I am sorry! Could not connect to the camera device. Please restart me or your phone.",
                        8000).show();
                Crittercism.logHandledException(e);
            }
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.d(TAG, "onSizeChanged() " + w + " " + h);
        fullWidth = w;
        fullHeight = h;
        if (fullWidth < fullHeight) {
            int tmp = fullHeight;
            fullHeight = fullWidth;
            fullWidth = tmp;
            Log.d(TAG, "switched:" + fullWidth + "x" + fullHeight);
        } else {
            Log.d(TAG, "fullSize:" + fullWidth + "x" + fullHeight);
        }
        if (this.camera != null) {
            this.setCameraPreviewSize();
            this.setCameraPictureSize();
            if (getChildCount() > 0) {
                final View child = getChildAt(0);
                Log.d(TAG, "r:" + this.getPreviewRight() + " l:" + this.getPreviewLeft() + " b:"
                        + this.getPreviewBottom() + " t:" + this.getPreviewTop());
                child.layout(this.getPreviewLeft(), this.getPreviewTop(), this.getPreviewRight(),
                        this.getPreviewBottom());
                cb.previewReady(getPreviewLeft(), getPreviewTop(), getPreviewRight() - getPreviewLeft(),
                        getPreviewBottom() - getPreviewTop(), getBestPictureSize().width,
                        getBestPictureSize().height, (int) downscalingFactor, allResolutions);

            }
        }

        super.onSizeChanged(w, h, oldw, oldh);
    }

    private void calcScaledPreviewSize() {
        int previewWidth = getBestPreviewSize().width;
        int previewHeight = getBestPreviewSize().height;
        float scaledWidth;
        float scaledHeight;

        Log.d(TAG, "preview width: " + previewWidth + ", preview height: " + previewHeight);
        Log.d(TAG, "display width: " + fullWidth + ", display height: " + fullHeight);
        float previewRatio = (float) previewWidth / (float) previewHeight;
        float displayRatio = (float) fullWidth / (float) fullHeight;

        if (displayRatio >= previewRatio) { // the display is wider then the
            // preview image
            scaledHeight = fullHeight - 2 * padding;
            scaledWidth = scaledHeight * previewRatio;
            l2 = (int) (fullWidth - scaledWidth) / 2;
            t2 = padding;
            r2 = (int) (fullWidth + scaledWidth) / 2;
            b2 = (int) scaledHeight + padding;

        } else {
            scaledWidth = fullWidth - 2 * padding;
            scaledHeight = scaledWidth / previewRatio;
            l2 = padding;
            t2 = (int) (fullHeight - scaledHeight) / 2;
            r2 = (int) scaledWidth + padding;
            b2 = (int) (fullHeight + scaledHeight) / 2;
        }
    }

    public int getPreviewTop() {
        if (this.t2 == 0) {
            this.calcScaledPreviewSize();
        }
        return t2;
    }

    public int getPreviewBottom() {
        if (this.b2 == 0) {
            this.calcScaledPreviewSize();
        }
        return b2;
    }

    public int getPreviewLeft() {
        if (this.l2 == 0) {
            this.calcScaledPreviewSize();
        }
        return l2;
    }

    public int getPreviewRight() {
        if (this.r2 == 0) {
            this.calcScaledPreviewSize();
        }
        return r2;
    }

    public int getPreviewWidth() {
        return this.getPreviewRight() - this.getPreviewLeft();
    }

    public int getPreviewHeight() {
        return this.getPreviewBottom() - this.getPreviewTop();
    }

    private void setCameraPreviewSize() {
        Camera.Parameters parameters = camera.getParameters();
        if (parameters.getPreviewSize() != this.getBestPreviewSize()) {
            parameters.setPreviewSize(this.getBestPreviewSize().width, this.getBestPreviewSize().height);
            this.camera.setParameters(parameters);
        }
    }

    private void setCameraPictureSize() {
        Camera.Parameters parameters = this.camera.getParameters();
        if (parameters.getPictureSize() != this.getBestPictureSize()) {
            parameters.setPictureSize(getBestPictureSize().width, getBestPictureSize().height);
            this.camera.setParameters(parameters);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        Log.d(TAG, "onLayout()");
        /*
         * if (changed && getChildCount() > 0 && this.camera != null) { final
         * View child = getChildAt(0);
         * Log.d(TAG,"r:"+this.getPreviewRight()+" l:"
         * +this.getPreviewLeft()+" b:"
         * +this.getPreviewBottom()+" t:"+this.getPreviewTop());
         * child.layout(this.getPreviewLeft(), this.getPreviewTop(),
         * this.getPreviewRight(), this.getPreviewBottom());
         * cameraActivity.initClap
         * (this.getPreviewLeft(),this.getPreviewTop(),
         * this.getPreviewRight(),this.getPreviewBottom()); }
         */
    }

    public Size getBestPictureSize() {
        if (this.bestPictureSize == null) {
            this.calculateOptimalPictureAndPreviewSizes();
        }
        return bestPictureSize;
    }

    public Size getBestPreviewSize() {
        if (this.bestPreviewSize == null) {
            this.calculateOptimalPictureAndPreviewSizes();
        }
        return bestPreviewSize;
    }

    Hashtable<Double, Size> bestPictureSizes = new Hashtable<Double, Size>();
    Hashtable<Double, Size> bestPreviewSizes = new Hashtable<Double, Size>();

    /**
     * find the best resolution for both: picture and preview size by following
     * rules ISSUES: 1. on each device you have several different camera picture
     * sizes and camera preview sizes 2. the picture size and the camera size
     * have to match in terms of their width to height relation (=aspect ratio)
     * 3. we want to make sure the area of the display is used as efficient as
     * possible, therefore we should try to match the aspect ratios of display
     * and image as well 4. you never know how much RAM you will have available,
     * once the user switches the app and returnes, memory might not be enough
     * anymore
     * 
     * VERY NEW: 1. find all preview sizes which match a image size (same aspect
     * ratio) -> resolution-settings 2. preselect the highest
     * resolution-settings which fits twice into memory 3. generate UI for
     * selecting other resolutions-settings-> let the user decide which
     * resolution fits best for his/her device
     * 
     * NEW: 1. calculate targetRato = device display aspect ratio 2. find a
     * matching preview image size and set it as bestPictureSize (matching: the
     * picture and preview aspect ratios have to match) 3. if a another
     * pictureSize-previewSize match is found, which has a better targetRatio
     * match and the picture size > bestPictureSize - 20% -> set this one as
     * bestPictureSize 4. try to find a downscaling factor which allows us to
     * keep 2-3 layers (bitmap instances) of that resolution in memory
     * 
     * OLD: 1. try to find the best matching picture resolution for targetRatio
     * 2. only allow resolutions above 800px width 3. look for an exactly
     * matching preview resolution. if not found: 4. try to find the second best
     * matching picture resolution for targetRatio and start all over again 5.
     * if nothing matching was found: use first picture and first preview
     * resolution
     * 
     * @param targetRatio
     */
    private void calculateOptimalPictureAndPreviewSizes() {

        /*
         * double fullWidth = (double) cameraActivity.display.getWidth(); double
         * fullHeight = (double) cameraActivity.display.getHeight();
         */
        double targetRatio = (fullWidth - 2 * (double) padding) / (fullHeight - 2 * (double) padding);
        Log.v(TAG, "calculateOptimalPictureAndPreviewSizes() width targetRatio: " + targetRatio + " fullWidth:"
                + fullWidth + " fullHeight:" + fullHeight);

        if (bestPreviewSize == null) {
            allResolutions = "";
            List<Size> pictureSizes = this.camera.getParameters().getSupportedPictureSizes();
            List<Size> previewSizes = this.camera.getParameters().getSupportedPreviewSizes();
            Collections.sort(pictureSizes, new Comparator<Size>() {
                public int compare(Size s1, Size s2) {
                    return s2.width - s1.width;
                }
            });

            Collections.sort(previewSizes, new Comparator<Size>() {
                public int compare(Size s1, Size s2) {
                    return s2.width - s1.width;
                }
            });

            allResolutions += "picture sizes:\n";
            for (Size size : pictureSizes) {
                allResolutions += String.valueOf(size.width) + 'x' + String.valueOf(size.height) + ", ratio: "
                        + ((double) size.width / (double) size.height) + ";\n";
            }

            allResolutions += "preview sizes:\n";
            for (Size size : previewSizes) {
                allResolutions += String.valueOf(size.width) + 'x' + String.valueOf(size.height) + ", ratio: "
                        + ((double) size.width / (double) size.height) + ";\n";
            }
            Log.v(TAG, "allResolutions: \n" + allResolutions);
            double bestRatio = 0;
            boolean matchingFinished = false;
            Log.v(TAG, "start matching picture and preview size...");
            for (Size pictureSize : pictureSizes) {
                double pictureRatio = (double) pictureSize.width / (double) pictureSize.height;
                Log.v(TAG, "size: " + pictureSize.width + "x" + pictureSize.height + " " + pictureRatio);
                double previewRatio;
                for (Size previewSize : previewSizes) {
                    previewRatio = (double) previewSize.width / (double) previewSize.height;
                    if (previewRatio == pictureRatio) {

                        if (bestPreviewSize == null) {
                            bestPreviewSize = previewSize;
                            bestPictureSize = pictureSize;
                            bestRatio = pictureRatio;
                            Log.v(TAG,
                                    "found picture size:" + bestPictureSize.width + "x" + bestPictureSize.height
                                            + " ratio: "
                                            + ((double) bestPictureSize.width / (double) bestPictureSize.height));
                            Log.v(TAG, "...continue searching...");
                            break;
                        } else {
                            Log.v(TAG, "  pixels: " + pictureSize.width * pictureSize.height);
                            Log.v(TAG, "  thresh: "
                                    + (double) bestPictureSize.width * (double) bestPictureSize.height * 0.75D);
                            if (Math.abs(targetRatio - bestRatio) > Math.abs(targetRatio - pictureRatio)
                                    && pictureSize.width * pictureSize.height > (double) bestPictureSize.width
                                            * (double) bestPictureSize.height * 0.75D) {
                                bestPreviewSize = previewSize;
                                bestPictureSize = pictureSize;
                                bestRatio = pictureRatio;
                                matchingFinished = true;
                                Log.v(TAG, "found even better match:" + bestPictureSize.width + "x"
                                        + bestPictureSize.height + " ratio: "
                                        + ((double) bestPictureSize.width / (double) bestPictureSize.height));
                            }
                        }
                    }
                }
                if (matchingFinished) {
                    break;
                }
            }

            if (bestPreviewSize == null) {
                bestPictureSize = pictureSizes.get(0);
                bestPreviewSize = previewSizes.get(0);
                Log.v(TAG, "no match found!");
            }

            downscalingFactor = Tools.getDownscalingFactor(bestPictureSize.width, bestPictureSize.height);

            Log.v(TAG, "choosen picture size:" + bestPictureSize.width + "x" + bestPictureSize.height + " ratio: "
                    + ((double) bestPictureSize.width / (double) bestPictureSize.height));
            Log.v(TAG, "choosen preview size:" + bestPreviewSize.width + "x" + bestPreviewSize.height + " ratio: "
                    + ((double) bestPreviewSize.width / (double) bestPreviewSize.height));
            Log.v(TAG, "choosen downScalingFactor: " + downscalingFactor);
            // instantiate metadata json object
            JSONObject metadata = new JSONObject();
            MemoryInfo mi = new MemoryInfo();
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Activity.ACTIVITY_SERVICE);
            activityManager.getMemoryInfo(mi);
            final long mb = 1024L * 1024L;
            try {
                metadata.put("totalMemory", Runtime.getRuntime().totalMemory() / mb);
                metadata.put("freeMemory", Runtime.getRuntime().freeMemory() / mb);
                metadata.put("maxMemory", Runtime.getRuntime().maxMemory() / mb);
                metadata.put("availableMemory", mi.availMem / mb);
                metadata.put("nativeHeapAllocatedSize", Debug.getNativeHeapAllocatedSize() / mb);
                metadata.put("heapPad", Tools.getHeapPad() / mb);
                metadata.put("bestPictureSize", bestPictureSize.width + "x" + bestPictureSize.height);
                metadata.put("bestPreviewSize", bestPreviewSize.width + "x" + bestPreviewSize.height);
                metadata.put("workingPictureSize", (int) bestPictureSize.width / downscalingFactor + "x"
                        + (int) bestPictureSize.height / downscalingFactor);
                metadata.put("downscalingFactor", downscalingFactor);
                metadata.put("requiredSize", (bestPictureSize.width / downscalingFactor)
                        * (bestPictureSize.height / downscalingFactor) * 4 * 2 / mb); // 4 channels * 2 layers
            } catch (JSONException e) {
                e.printStackTrace();
            }

            Crittercism.setMetadata(metadata);

        }

    }

    // Called once the holder is ready
    public void surfaceCreated(SurfaceHolder holder) { // <7>
        // The Surface has been created, acquire the camera and tell it where
        // to draw.
        Log.d(TAG, "surfaceCreated()");
        try {
            if (this.camera != null) {
                this.camera.setPreviewDisplay(holder);
            }
        } catch (IOException exception) {
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);

        }
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        Log.d(TAG, "surfaceChanged()");
        if (camera != null) {

            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(getBestPreviewSize().width, getBestPreviewSize().height);
            camera.setParameters(parameters);
            camera.startPreview();
            cb.onPreviewStart();
            requestLayout();
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) { // <14>
        Log.d(TAG, "surfaceDestroyed()");
        if (this.camera != null) {
            camera.stopPreview();
            camera.lock();
            camera.release();
            this.camera = null;
        }
    }

    public void releaseCamera() {
        Log.d(TAG, "releaseCamera()");
        if (camera != null) {
            camera.stopPreview();
            camera.setPreviewCallback(null);
            camera.release();
            camera = null;
        }
    }

}