Java tutorial
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; } } }