All pastes #3182401 Raw Edit

cameraSample

public unlisted java v1 · immutable
#3182401 ·published 2015-10-06 04:19 UTC
rendered paste body
/* * Copyright 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *       http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */import android.app.Activity;import android.app.AlertDialog;import android.app.Dialog;import android.app.DialogFragment;import android.content.Context;import android.content.DialogInterface;import android.graphics.ImageFormat;import android.hardware.camera2.CameraAccessException;import android.hardware.camera2.CameraCaptureSession;import android.hardware.camera2.CameraCharacteristics;import android.hardware.camera2.CameraDevice;import android.hardware.camera2.CameraManager;import android.hardware.camera2.CameraMetadata;import android.hardware.camera2.CaptureRequest;import android.hardware.camera2.CaptureResult;import android.hardware.camera2.TotalCaptureResult;import android.hardware.camera2.params.StreamConfigurationMap;import android.media.Image;import android.media.ImageReader;import android.os.Bundle;import android.os.Handler;import android.os.HandlerThread;import android.os.Message;import android.support.v4.app.Fragment;import android.util.Log;import android.util.Size;import android.util.SparseIntArray;import android.view.LayoutInflater;import android.view.Surface;import android.view.View;import android.view.ViewGroup;import android.widget.Toast;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.nio.ByteBuffer;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.Comparator;import java.util.List;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;import jlapps.timeslice.R;public class CameraFragment extends Fragment implements View.OnClickListener {    /**     * Conversion from screen rotation to JPEG orientation.     */    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();    static {        ORIENTATIONS.append(Surface.ROTATION_0, 90);        ORIENTATIONS.append(Surface.ROTATION_90, 0);        ORIENTATIONS.append(Surface.ROTATION_180, 270);        ORIENTATIONS.append(Surface.ROTATION_270, 180);    }    /**     * Tag for the {@link Log}.     */    private static final String TAG = "CameraFragment";    /**     * Camera state: Showing camera preview.     */    private static final int STATE_PREVIEW = 0;    /**     * Camera state: Waiting for the focus to be locked.     */    private static final int STATE_WAITING_LOCK = 1;    /**     * Camera state: Waiting for the exposure to be precapture state.     */    private static final int STATE_WAITING_PRECAPTURE = 2;    /**     * Camera state: Waiting for the exposure state to be something other than precapture.     */    private static final int STATE_WAITING_NON_PRECAPTURE = 3;    /**     * Camera state: Picture was taken.     */    private static final int STATE_PICTURE_TAKEN = 4;    /**     * ID of the current {@link CameraDevice}.     */    private String mCameraId;    /**     * A {@link CameraCaptureSession } for camera preview.     */    private CameraCaptureSession mCaptureSession;    /**     * A reference to the opened {@link CameraDevice}.     */    private CameraDevice mCameraDevice;    /**     * The {@link android.util.Size} of camera preview.     */    /**     * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.     */    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {        @Override        public void onOpened(CameraDevice cameraDevice) {            // This method is called when the camera is opened.  We start camera preview here.            mCameraOpenCloseLock.release();            mCameraDevice = cameraDevice;            createCameraPreviewSession();        }        @Override        public void onDisconnected(CameraDevice cameraDevice) {            mCameraOpenCloseLock.release();            cameraDevice.close();            mCameraDevice = null;        }        @Override        public void onError(CameraDevice cameraDevice, int error) {            mCameraOpenCloseLock.release();            cameraDevice.close();            mCameraDevice = null;            Activity activity = getActivity();            if (null != activity) {                activity.finish();            }        }    };    /**     * An additional thread for running tasks that shouldn't block the UI.     */    private HandlerThread mBackgroundThread;    /**     * A {@link Handler} for running tasks in the background.     */    private Handler mBackgroundHandler;    /**     * An {@link ImageReader} that handles still image capture.     */    private ImageReader mImageReader;    /**     * This is the output file for our picture.     */    private File mFile;    /**     * This a callback object for the {@link ImageReader}. "onImageAvailable" will be called when a     * still image is ready to be saved.     */    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener            = new ImageReader.OnImageAvailableListener() {        @Override        public void onImageAvailable(ImageReader reader) {            mBackgroundHandler.post(new jlapps.timeslice.imageProcessing.ImageSaver(reader.acquireNextImage(), mFile));        }    };    /**     * {@link CaptureRequest.Builder} for the camera preview     */    private CaptureRequest.Builder mPreviewRequestBuilder;    /**     * The current state of camera state for taking pictures.     *     * @see #mCaptureCallback     */    private int mState = STATE_PREVIEW;    /**     * A {@link Semaphore} to prevent the app from exiting before closing the camera.     */    private Semaphore mCameraOpenCloseLock = new Semaphore(1);    /**     * A {@link CameraCaptureSession.CaptureCallback} that handles events related to JPEG capture.     */    private CameraCaptureSession.CaptureCallback mCaptureCallback            = new CameraCaptureSession.CaptureCallback() {        private void process(CaptureResult result) {            Log.d(TAG, "process: " + mState);            switch (mState) {                case STATE_PREVIEW: {                    // We have nothing to do when the camera preview is working normally.                    break;                }                case STATE_WAITING_LOCK: {                    Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);                    if (afState == null) {                        captureStillPicture();                    } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||                            CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {                        // CONTROL_AE_STATE can be null on some devices                        Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);                        if (aeState == null ||                                aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {                            mState = STATE_PICTURE_TAKEN;                            captureStillPicture();                        } else {                            runPrecaptureSequence();                        }                    }                    break;                }                case STATE_WAITING_PRECAPTURE: {                    // CONTROL_AE_STATE can be null on some devices                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);                    if (aeState == null ||                            aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||                            aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {                        mState = STATE_WAITING_NON_PRECAPTURE;                    }                    break;                }                case STATE_WAITING_NON_PRECAPTURE: {                    // CONTROL_AE_STATE can be null on some devices                    Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);                    if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {                        mState = STATE_PICTURE_TAKEN;                        captureStillPicture();                    }                    break;                }            }        }        @Override        public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,                                        CaptureResult partialResult) {            Log.d(TAG, "onCaptureProgressed");            process(partialResult);        }        @Override        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,                                       TotalCaptureResult result) {            Log.d(TAG, "onCaptureCompleted");            process(result);        }    };    /**     * A {@link Handler} for showing {@link Toast}s.     */    private Handler mMessageHandler = new Handler() {        @Override        public void handleMessage(Message msg) {            Activity activity = getActivity();            if (activity != null) {                Toast.makeText(activity, (String) msg.obj, Toast.LENGTH_SHORT).show();            }        }    };    /**     * Shows a {@link Toast} on the UI thread.     *     * @param text The message to show     */    private void showToast(String text) {        // We show a Toast by sending request message to mMessageHandler. This makes sure that the        // Toast is shown on the UI thread.        Message message = Message.obtain();        message.obj = text;        mMessageHandler.sendMessage(message);    }    /**     * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose     * width and height are at least as large as the respective requested values, and whose aspect     * ratio matches with the specified value.     *     * @param choices     The list of sizes that the camera supports for the intended output class     * @param width       The minimum desired width     * @param height      The minimum desired height     * @param aspectRatio The aspect ratio     * @return The optimal {@code Size}, or an arbitrary one if none were big enough     */    private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) {        // Collect the supported resolutions that are at least as big as the preview Surface        List<Size> bigEnough = new ArrayList<Size>();        int w = aspectRatio.getWidth();        int h = aspectRatio.getHeight();        for (Size option : choices) {            if (option.getHeight() == option.getWidth() * h / w &&                    option.getWidth() >= width && option.getHeight() >= height) {                bigEnough.add(option);            }        }        // Pick the smallest of those, assuming we found any        if (bigEnough.size() > 0) {            return Collections.min(bigEnough, new CompareSizesByArea());        } else {            Log.e(TAG, "Couldn't find any suitable preview size");            return choices[0];        }    }    public static CameraFragment newInstance() {        CameraFragment fragment = new CameraFragment();        fragment.setRetainInstance(true);        return fragment;    }    @Override    public View onCreateView(LayoutInflater inflater, ViewGroup container,                             Bundle savedInstanceState) {        return inflater.inflate(R.layout.fragment_camera, container, false);    }    @Override    public void onViewCreated(final View view, Bundle savedInstanceState) {        view.findViewById(R.id.picture_button).setOnClickListener(this);        view.findViewById(R.id.settings_button).setOnClickListener(this);    }    @Override    public void onActivityCreated(Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        mFile = new File(getActivity().getExternalFilesDir(null), "pic.jpg");    }    @Override    public void onResume() {        super.onResume();        startBackgroundThread();        openCamera(1,1);    }    @Override    public void onPause() {        closeCamera();        stopBackgroundThread();        super.onPause();    }    /**     * Sets up member variables related to camera.     *     * @param width  The width of available size for camera preview     * @param height The height of available size for camera preview     */    private void setUpCameraOutputs(int width, int height) {        Activity activity = getActivity();        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);        try {            for (String cameraId : manager.getCameraIdList()) {                CameraCharacteristics characteristics                        = manager.getCameraCharacteristics(cameraId);                // We don't use a front facing camera in this sample.                if (characteristics.get(CameraCharacteristics.LENS_FACING)                        == CameraCharacteristics.LENS_FACING_FRONT) {                    continue;                }                StreamConfigurationMap map = characteristics.get(                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);                // For still image captures, we use the largest available size.                Size largest = Collections.max(                        Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),                        new CompareSizesByArea());                mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, /*maxImages*/2);                mImageReader.setOnImageAvailableListener( mOnImageAvailableListener, mBackgroundHandler);                mCameraId = cameraId;                return;            }        } catch (CameraAccessException e) {            e.printStackTrace();        } catch (NullPointerException e) {            // Currently an NPE is thrown when the Camera2API is used but not supported on the            // device this code runs.            //new ErrorDialog().show(getFragmentManager(), "dialog");            e.printStackTrace();        }    }    /**     * Opens the camera specified by {@link CameraFragment#mCameraId}.     */    private void openCamera(int width, int height) {        setUpCameraOutputs(width, height);        Activity activity = getActivity();        CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);        try {            if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {                throw new RuntimeException("Time out waiting to lock camera opening.");            }            manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);        } catch (CameraAccessException e) {            e.printStackTrace();        } catch (InterruptedException e) {            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);        }    }    /**     * Closes the current {@link CameraDevice}.     */    private void closeCamera() {        try {            mCameraOpenCloseLock.acquire();            if (null != mCaptureSession) {                mCaptureSession.close();                mCaptureSession = null;            }            if (null != mCameraDevice) {                mCameraDevice.close();                mCameraDevice = null;            }            if (null != mImageReader) {                mImageReader.close();                mImageReader = null;            }        } catch (InterruptedException e) {            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);        } finally {            mCameraOpenCloseLock.release();        }    }    /**     * Starts a background thread and its {@link Handler}.     */    private void startBackgroundThread() {        mBackgroundThread = new HandlerThread("CameraBackground");        mBackgroundThread.start();        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());    }    /**     * Stops the background thread and its {@link Handler}.     */    private void stopBackgroundThread() {        mBackgroundThread.quitSafely();        try {            mBackgroundThread.join();            mBackgroundThread = null;            mBackgroundHandler = null;        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * Creates a new {@link CameraCaptureSession} for camera preview.     */    private void createCameraPreviewSession() {        try {            // We set up a CaptureRequest.Builder with the output Surface.            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);            mPreviewRequestBuilder.addTarget(mImageReader.getSurface());            // Here, we create a CameraCaptureSession for camera preview.            mCameraDevice.createCaptureSession(Arrays.asList(mImageReader.getSurface()),                    new CameraCaptureSession.StateCallback() {                        @Override                        public void onConfigured(CameraCaptureSession cameraCaptureSession) {                            // The camera is already closed                            if (null == mCameraDevice) {                                return;                            }                            // When the session is ready, we start displaying the preview.                            mCaptureSession = cameraCaptureSession;                            /*                            try {                                // Auto focus should be continuous for camera preview.                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);                                // Flash is automatically enabled when necessary.                                mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,                                        CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);                                // Finally, we start displaying the camera preview.                                mPreviewRequest = mPreviewRequestBuilder.build();                                mCaptureSession.setRepeatingRequest(mPreviewRequest,                                        mCaptureCallback, mBackgroundHandler);                            } catch (CameraAccessException e) {                                e.printStackTrace();                            }                            */                        }                        @Override                        public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {                            showToast("Failed");                        }                    }, null            );        } catch (CameraAccessException e) {            e.printStackTrace();        }    }    /**     * Initiate a still image capture.     */    private void takePicture() {        lockFocus();    }    /**     * Lock the focus as the first step for a still image capture.     */    private void lockFocus() {        try {            // This is how to tell the camera to lock focus.            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,                    CameraMetadata.CONTROL_AF_TRIGGER_START);            // Tell #mCaptureCallback to wait for the lock.            mState = STATE_WAITING_LOCK;            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,                    mBackgroundHandler);        } catch (CameraAccessException e) {            e.printStackTrace();        }    }    /**     * Run the precapture sequence for capturing a still image. This method should be called when we     * get a response in {@link #mCaptureCallback} from {@link #lockFocus()}.     */    private void runPrecaptureSequence() {        try {            // This is how to tell the camera to trigger.            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,                    CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);            // Tell #mCaptureCallback to wait for the precapture sequence to be set.            mState = STATE_WAITING_PRECAPTURE;            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,                    mBackgroundHandler);        } catch (CameraAccessException e) {            e.printStackTrace();        }    }    /**     * Capture a still picture. This method should be called when we get a response in     * {@link #mCaptureCallback} from both {@link #lockFocus()}.     */    private void captureStillPicture() {        try {            final Activity activity = getActivity();            if (null == activity || null == mCameraDevice) {                return;            }            // This is the CaptureRequest.Builder that we use to take a picture.            final CaptureRequest.Builder captureBuilder =                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);            captureBuilder.addTarget(mImageReader.getSurface());            // Use the same AE and AF modes as the preview.            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);            captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);            // Orientation            int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));            CameraCaptureSession.CaptureCallback CaptureCallback                    = new CameraCaptureSession.CaptureCallback() {                @Override                public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,                                               TotalCaptureResult result) {                    showToast("Saved: " + mFile);                    unlockFocus();                }            };            mCaptureSession.stopRepeating();            mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);        } catch (CameraAccessException e) {            e.printStackTrace();        }    }    /**     * Unlock the focus. This method should be called when still image capture sequence is finished.     */    private void unlockFocus() {        try {            // Reset the autofucos trigger            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,                    CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);            mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,                    mBackgroundHandler);            // After this, the camera will go back to the normal state of preview.            mState = STATE_PREVIEW;        } catch (CameraAccessException e) {            e.printStackTrace();        }    }    @Override    public void onClick(View view) {        switch (view.getId()) {            case R.id.picture_button: {                takePicture();                break;            }            /*            case R.id.info: {                Activity activity = getActivity();                if (null != activity) {                    new AlertDialog.Builder(activity)                            .setMessage(R.string.intro_message)                            .setPositiveButton(android.R.string.ok, null)                            .show();                }                break;            }            */        }    }    /**     * Saves a JPEG {@link Image} into the specified {@link File}.     */    private static class ImageSaver implements Runnable {        /**         * The JPEG image         */        private final Image mImage;        /**         * The file we save the image into.         */        private final File mFile;        public ImageSaver(Image image, File file) {            mImage = image;            mFile = file;        }        @Override        public void run() {            ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();            byte[] bytes = new byte[buffer.remaining()];            buffer.get(bytes);            FileOutputStream output = null;            try {                output = new FileOutputStream(mFile);                output.write(bytes);            } catch (IOException e) {                e.printStackTrace();            } finally {                mImage.close();                if (null != output) {                    try {                        output.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }        }    }    /**     * Compares two {@code Size}s based on their areas.     */    static class CompareSizesByArea implements Comparator<Size> {        @Override        public int compare(Size lhs, Size rhs) {            // We cast here to ensure the multiplications won't overflow            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -                    (long) rhs.getWidth() * rhs.getHeight());        }    }    public static class ErrorDialog extends DialogFragment {        @Override        public Dialog onCreateDialog(Bundle savedInstanceState) {            final Activity activity = getActivity();            return new AlertDialog.Builder(activity)                    .setMessage("This device doesn't support Camera2 API.")                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {                        @Override                        public void onClick(DialogInterface dialogInterface, int i) {                            activity.finish();                        }                    })                    .create();        }    }}