| /* |
| * Copyright (C) 2012 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. |
| */ |
| |
| package com.android.testingcamera; |
| |
| import android.annotation.SuppressLint; |
| import android.app.Activity; |
| import android.app.FragmentManager; |
| import android.content.res.Resources; |
| import android.graphics.ImageFormat; |
| import android.hardware.Camera; |
| import android.hardware.Camera.Parameters; |
| import android.hardware.Camera.ErrorCallback; |
| import android.media.CamcorderProfile; |
| import android.media.MediaRecorder; |
| import android.media.MediaScannerConnection; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.view.View; |
| import android.view.Surface; |
| import android.view.SurfaceHolder; |
| import android.view.SurfaceView; |
| import android.view.View.OnClickListener; |
| import android.widget.AdapterView; |
| import android.widget.AdapterView.OnItemSelectedListener; |
| import android.widget.ArrayAdapter; |
| import android.widget.Button; |
| import android.widget.CheckBox; |
| import android.widget.LinearLayout; |
| import android.widget.LinearLayout.LayoutParams; |
| import android.widget.SeekBar; |
| import android.widget.Spinner; |
| import android.widget.TextView; |
| import android.widget.ToggleButton; |
| import android.renderscript.RenderScript; |
| import android.text.Layout; |
| import android.text.method.ScrollingMovementMethod; |
| import android.util.Log; |
| import android.util.SparseArray; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * A simple test application for the camera API. |
| * |
| * The goal of this application is to allow all camera API features to be |
| * exercised, and all information provided by the API to be shown. |
| */ |
| public class TestingCamera extends Activity |
| implements SurfaceHolder.Callback, Camera.PreviewCallback, |
| Camera.ErrorCallback { |
| |
| /** UI elements */ |
| private SurfaceView mPreviewView; |
| private SurfaceHolder mPreviewHolder; |
| private LinearLayout mPreviewColumn; |
| |
| private SurfaceView mCallbackView; |
| private SurfaceHolder mCallbackHolder; |
| |
| private Spinner mCameraSpinner; |
| private CheckBox mKeepOpenCheckBox; |
| private Button mInfoButton; |
| private Spinner mPreviewSizeSpinner; |
| private Spinner mPreviewFrameRateSpinner; |
| private ToggleButton mPreviewToggle; |
| private ToggleButton mHDRToggle; |
| private Spinner mAutofocusModeSpinner; |
| private Button mAutofocusButton; |
| private Button mCancelAutofocusButton; |
| private TextView mFlashModeSpinnerLabel; |
| private Spinner mFlashModeSpinner; |
| private ToggleButton mExposureLockToggle; |
| private Spinner mSnapshotSizeSpinner; |
| private Button mTakePictureButton; |
| private Spinner mCamcorderProfileSpinner; |
| private Spinner mVideoRecordSizeSpinner; |
| private Spinner mVideoFrameRateSpinner; |
| private ToggleButton mRecordToggle; |
| private CheckBox mRecordHandoffCheckBox; |
| private ToggleButton mRecordStabilizationToggle; |
| private ToggleButton mRecordHintToggle; |
| private ToggleButton mLockCameraToggle; |
| private Spinner mCallbackFormatSpinner; |
| private ToggleButton mCallbackToggle; |
| private TextView mColorEffectSpinnerLabel; |
| private Spinner mColorEffectSpinner; |
| private SeekBar mZoomSeekBar; |
| |
| private TextView mLogView; |
| |
| private Set<View> mOpenOnlyControls = new HashSet<View>(); |
| private Set<View> mPreviewOnlyControls = new HashSet<View>(); |
| |
| private SparseArray<String> mFormatNames; |
| |
| /** Camera state */ |
| private int mCameraId; |
| private Camera mCamera; |
| private Camera.Parameters mParams; |
| private List<Camera.Size> mPreviewSizes; |
| private int mPreviewSize = 0; |
| private List<Integer> mPreviewFrameRates; |
| private int mPreviewFrameRate = 0; |
| private List<Integer> mPreviewFormats; |
| private int mPreviewFormat = 0; |
| private List<String> mAfModes; |
| private int mAfMode = 0; |
| private List<String> mFlashModes; |
| private int mFlashMode = 0; |
| private List<Camera.Size> mSnapshotSizes; |
| private int mSnapshotSize = 0; |
| private List<CamcorderProfile> mCamcorderProfiles; |
| private int mCamcorderProfile = 0; |
| private List<Camera.Size> mVideoRecordSizes; |
| private int mVideoRecordSize = 0; |
| private List<Integer> mVideoFrameRates; |
| private int mVideoFrameRate = 0; |
| private List<String> mColorEffects; |
| private int mColorEffect = 0; |
| private int mZoom = 0; |
| |
| private MediaRecorder mRecorder; |
| private File mRecordingFile; |
| |
| private RenderScript mRS; |
| |
| private boolean mCallbacksEnabled = false; |
| private CallbackProcessor mCallbackProcessor = null; |
| long mLastCallbackTimestamp = -1; |
| float mCallbackAvgFrameDuration = 30; |
| int mCallbackFrameCount = 0; |
| private static final float MEAN_FPS_HISTORY_COEFF = 0.9f; |
| private static final float MEAN_FPS_MEASUREMENT_COEFF = 0.1f; |
| private static final int FPS_REPORTING_PERIOD = 200; // frames |
| private static final int CALLBACK_BUFFER_COUNT = 3; |
| |
| private static final int CAMERA_UNINITIALIZED = 0; |
| private static final int CAMERA_OPEN = 1; |
| private static final int CAMERA_PREVIEW = 2; |
| private static final int CAMERA_TAKE_PICTURE = 3; |
| private static final int CAMERA_RECORD = 4; |
| private int mState = CAMERA_UNINITIALIZED; |
| |
| private static final int NO_CAMERA_ID = -1; |
| |
| /** Misc variables */ |
| |
| private static final String TAG = "TestingCamera"; |
| |
| |
| /** Activity lifecycle */ |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| |
| setContentView(R.layout.main); |
| |
| mPreviewColumn = (LinearLayout) findViewById(R.id.preview_column); |
| |
| mPreviewView = (SurfaceView) findViewById(R.id.preview); |
| mPreviewView.getHolder().addCallback(this); |
| |
| mCallbackView = (SurfaceView)findViewById(R.id.callback_view); |
| |
| mCameraSpinner = (Spinner) findViewById(R.id.camera_spinner); |
| mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener); |
| |
| mKeepOpenCheckBox = (CheckBox) findViewById(R.id.keep_open_checkbox); |
| |
| mInfoButton = (Button) findViewById(R.id.info_button); |
| mInfoButton.setOnClickListener(mInfoButtonListener); |
| mOpenOnlyControls.add(mInfoButton); |
| |
| mPreviewSizeSpinner = (Spinner) findViewById(R.id.preview_size_spinner); |
| mPreviewSizeSpinner.setOnItemSelectedListener(mPreviewSizeListener); |
| mOpenOnlyControls.add(mPreviewSizeSpinner); |
| |
| mPreviewFrameRateSpinner = (Spinner) findViewById(R.id.preview_frame_rate_spinner); |
| mPreviewFrameRateSpinner.setOnItemSelectedListener(mPreviewFrameRateListener); |
| mOpenOnlyControls.add(mPreviewFrameRateSpinner); |
| |
| mHDRToggle = (ToggleButton) findViewById(R.id.hdr_mode); |
| mHDRToggle.setOnClickListener(mHDRToggleListener); |
| mOpenOnlyControls.add(mHDRToggle); |
| |
| mPreviewToggle = (ToggleButton) findViewById(R.id.start_preview); |
| mPreviewToggle.setOnClickListener(mPreviewToggleListener); |
| mOpenOnlyControls.add(mPreviewToggle); |
| |
| mAutofocusModeSpinner = (Spinner) findViewById(R.id.af_mode_spinner); |
| mAutofocusModeSpinner.setOnItemSelectedListener(mAutofocusModeListener); |
| mOpenOnlyControls.add(mAutofocusModeSpinner); |
| |
| mAutofocusButton = (Button) findViewById(R.id.af_button); |
| mAutofocusButton.setOnClickListener(mAutofocusButtonListener); |
| mPreviewOnlyControls.add(mAutofocusButton); |
| |
| mCancelAutofocusButton = (Button) findViewById(R.id.af_cancel_button); |
| mCancelAutofocusButton.setOnClickListener(mCancelAutofocusButtonListener); |
| mPreviewOnlyControls.add(mCancelAutofocusButton); |
| |
| mFlashModeSpinnerLabel = (TextView) findViewById(R.id.flash_mode_spinner_label); |
| |
| mFlashModeSpinner = (Spinner) findViewById(R.id.flash_mode_spinner); |
| mFlashModeSpinner.setOnItemSelectedListener(mFlashModeListener); |
| mOpenOnlyControls.add(mFlashModeSpinner); |
| |
| mExposureLockToggle = (ToggleButton) findViewById(R.id.exposure_lock); |
| mExposureLockToggle.setOnClickListener(mExposureLockToggleListener); |
| mOpenOnlyControls.add(mExposureLockToggle); |
| |
| mZoomSeekBar = (SeekBar) findViewById(R.id.zoom_seekbar); |
| mZoomSeekBar.setOnSeekBarChangeListener(mZoomSeekBarListener); |
| |
| mSnapshotSizeSpinner = (Spinner) findViewById(R.id.snapshot_size_spinner); |
| mSnapshotSizeSpinner.setOnItemSelectedListener(mSnapshotSizeListener); |
| mOpenOnlyControls.add(mSnapshotSizeSpinner); |
| |
| mTakePictureButton = (Button) findViewById(R.id.take_picture); |
| mTakePictureButton.setOnClickListener(mTakePictureListener); |
| mPreviewOnlyControls.add(mTakePictureButton); |
| |
| mCamcorderProfileSpinner = (Spinner) findViewById(R.id.camcorder_profile_spinner); |
| mCamcorderProfileSpinner.setOnItemSelectedListener(mCamcorderProfileListener); |
| mOpenOnlyControls.add(mCamcorderProfileSpinner); |
| |
| mVideoRecordSizeSpinner = (Spinner) findViewById(R.id.video_record_size_spinner); |
| mVideoRecordSizeSpinner.setOnItemSelectedListener(mVideoRecordSizeListener); |
| mOpenOnlyControls.add(mVideoRecordSizeSpinner); |
| |
| mVideoFrameRateSpinner = (Spinner) findViewById(R.id.video_frame_rate_spinner); |
| mVideoFrameRateSpinner.setOnItemSelectedListener(mVideoFrameRateListener); |
| mOpenOnlyControls.add(mVideoFrameRateSpinner); |
| |
| mRecordToggle = (ToggleButton) findViewById(R.id.start_record); |
| mRecordToggle.setOnClickListener(mRecordToggleListener); |
| mPreviewOnlyControls.add(mRecordToggle); |
| |
| mRecordHandoffCheckBox = (CheckBox) findViewById(R.id.record_handoff_checkbox); |
| |
| mRecordStabilizationToggle = (ToggleButton) findViewById(R.id.record_stabilization); |
| mRecordStabilizationToggle.setOnClickListener(mRecordStabilizationToggleListener); |
| mOpenOnlyControls.add(mRecordStabilizationToggle); |
| |
| mRecordHintToggle = (ToggleButton) findViewById(R.id.record_hint); |
| mRecordHintToggle.setOnClickListener(mRecordHintToggleListener); |
| mOpenOnlyControls.add(mRecordHintToggle); |
| |
| mLockCameraToggle = (ToggleButton) findViewById(R.id.lock_camera); |
| mLockCameraToggle.setOnClickListener(mLockCameraToggleListener); |
| mLockCameraToggle.setChecked(true); // ON by default |
| mOpenOnlyControls.add(mLockCameraToggle); |
| |
| mCallbackFormatSpinner = (Spinner) findViewById(R.id.callback_format_spinner); |
| mCallbackFormatSpinner.setOnItemSelectedListener(mCallbackFormatListener); |
| mOpenOnlyControls.add(mCallbackFormatSpinner); |
| |
| mCallbackToggle = (ToggleButton) findViewById(R.id.enable_callbacks); |
| mCallbackToggle.setOnClickListener(mCallbackToggleListener); |
| mOpenOnlyControls.add(mCallbackToggle); |
| |
| mColorEffectSpinnerLabel = (TextView) findViewById(R.id.color_effect_spinner_label); |
| |
| mColorEffectSpinner = (Spinner) findViewById(R.id.color_effect_spinner); |
| mColorEffectSpinner.setOnItemSelectedListener(mColorEffectListener); |
| mOpenOnlyControls.add(mColorEffectSpinner); |
| |
| mLogView = (TextView) findViewById(R.id.log); |
| mLogView.setMovementMethod(new ScrollingMovementMethod()); |
| |
| mOpenOnlyControls.addAll(mPreviewOnlyControls); |
| |
| mFormatNames = new SparseArray<String>(7); |
| mFormatNames.append(ImageFormat.JPEG, "JPEG"); |
| mFormatNames.append(ImageFormat.NV16, "NV16"); |
| mFormatNames.append(ImageFormat.NV21, "NV21"); |
| mFormatNames.append(ImageFormat.RGB_565, "RGB_565"); |
| mFormatNames.append(ImageFormat.UNKNOWN, "UNKNOWN"); |
| mFormatNames.append(ImageFormat.YUY2, "YUY2"); |
| mFormatNames.append(ImageFormat.YV12, "YV12"); |
| |
| int numCameras = Camera.getNumberOfCameras(); |
| String[] cameraNames = new String[numCameras + 1]; |
| cameraNames[0] = "None"; |
| for (int i = 0; i < numCameras; i++) { |
| cameraNames[i + 1] = "Camera " + i; |
| } |
| |
| mCameraSpinner.setAdapter( |
| new ArrayAdapter<String>(this, |
| R.layout.spinner_item, cameraNames)); |
| if (numCameras > 0) { |
| mCameraId = 0; |
| mCameraSpinner.setSelection(mCameraId + 1); |
| } else { |
| resetCamera(); |
| mCameraSpinner.setSelection(0); |
| } |
| |
| mRS = RenderScript.create(this); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| log("onResume: Setting up"); |
| mPreviewHolder = null; |
| setUpCamera(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| if (mState == CAMERA_RECORD) { |
| stopRecording(false); |
| } |
| if (mKeepOpenCheckBox.isChecked()) { |
| log("onPause: Not releasing camera"); |
| |
| if (mState == CAMERA_PREVIEW) { |
| mCamera.stopPreview(); |
| mState = CAMERA_OPEN; |
| } |
| } else { |
| log("onPause: Releasing camera"); |
| |
| if (mCamera != null) { |
| mCamera.release(); |
| } |
| mState = CAMERA_UNINITIALIZED; |
| } |
| } |
| |
| /** SurfaceHolder.Callback methods */ |
| @Override |
| public void surfaceChanged(SurfaceHolder holder, |
| int format, |
| int width, |
| int height) { |
| if (holder == mPreviewView.getHolder()) { |
| if (mState >= CAMERA_OPEN) { |
| final int previewWidth = |
| mPreviewSizes.get(mPreviewSize).width; |
| final int previewHeight = |
| mPreviewSizes.get(mPreviewSize).height; |
| |
| if ( Math.abs((float)previewWidth / previewHeight - |
| (float)width/height) > 0.01f) { |
| Handler h = new Handler(); |
| h.post(new Runnable() { |
| @Override |
| public void run() { |
| layoutPreview(); |
| } |
| }); |
| } |
| } |
| |
| if (mPreviewHolder != null) { |
| return; |
| } |
| log("Surface holder available: " + width + " x " + height); |
| mPreviewHolder = holder; |
| try { |
| if (mCamera != null) { |
| mCamera.setPreviewDisplay(holder); |
| } |
| } catch (IOException e) { |
| logE("Unable to set up preview!"); |
| } |
| } else if (holder == mCallbackView.getHolder()) { |
| mCallbackHolder = holder; |
| } |
| } |
| |
| @Override |
| public void surfaceCreated(SurfaceHolder holder) { |
| |
| } |
| |
| @Override |
| public void surfaceDestroyed(SurfaceHolder holder) { |
| mPreviewHolder = null; |
| } |
| |
| public void setCameraDisplayOrientation() { |
| android.hardware.Camera.CameraInfo info = |
| new android.hardware.Camera.CameraInfo(); |
| android.hardware.Camera.getCameraInfo(mCameraId, info); |
| int rotation = getWindowManager().getDefaultDisplay() |
| .getRotation(); |
| int degrees = 0; |
| switch (rotation) { |
| case Surface.ROTATION_0: degrees = 0; break; |
| case Surface.ROTATION_90: degrees = 90; break; |
| case Surface.ROTATION_180: degrees = 180; break; |
| case Surface.ROTATION_270: degrees = 270; break; |
| } |
| |
| int result; |
| if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { |
| result = (info.orientation + degrees) % 360; |
| result = (360 - result) % 360; // compensate the mirror |
| } else { // back-facing |
| result = (info.orientation - degrees + 360) % 360; |
| } |
| log(String.format( |
| "Camera sensor orientation %d, UI rotation %d, facing %s. Final orientation %d", |
| info.orientation, rotation, |
| info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT ? "FRONT" : "BACK", |
| result)); |
| mCamera.setDisplayOrientation(result); |
| } |
| |
| /** UI controls enable/disable for all open-only controls */ |
| private void enableOpenOnlyControls(boolean enabled) { |
| for (View v : mOpenOnlyControls) { |
| v.setEnabled(enabled); |
| } |
| } |
| |
| /** UI controls enable/disable for all preview-only controls */ |
| private void enablePreviewOnlyControls(boolean enabled) { |
| for (View v : mPreviewOnlyControls) { |
| v.setEnabled(enabled); |
| } |
| } |
| |
| /** UI listeners */ |
| |
| private AdapterView.OnItemSelectedListener mCameraSpinnerListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| int cameraId = pos - 1; |
| if (mCameraId != cameraId) { |
| resetCamera(); |
| mCameraId = cameraId; |
| mPreviewToggle.setChecked(false); |
| setUpCamera(); |
| } |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private OnClickListener mInfoButtonListener = new OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (mCameraId != NO_CAMERA_ID) { |
| FragmentManager fm = getFragmentManager(); |
| InfoDialogFragment infoDialog = new InfoDialogFragment(); |
| infoDialog.updateInfo(mCameraId, mCamera); |
| infoDialog.show(fm, "info_dialog_fragment"); |
| } |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mPreviewSizeListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mPreviewSize) return; |
| if (mState == CAMERA_PREVIEW) { |
| log("Stopping preview and callbacks to switch resolutions"); |
| stopCallbacks(); |
| mCamera.stopPreview(); |
| } |
| |
| mPreviewSize = pos; |
| int width = mPreviewSizes.get(mPreviewSize).width; |
| int height = mPreviewSizes.get(mPreviewSize).height; |
| mParams.setPreviewSize(width, height); |
| |
| log("Setting preview size to " + width + "x" + height); |
| |
| mCamera.setParameters(mParams); |
| resizePreview(); |
| |
| if (mState == CAMERA_PREVIEW) { |
| log("Restarting preview"); |
| mCamera.startPreview(); |
| } |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mPreviewFrameRateListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mPreviewFrameRate) return; |
| mPreviewFrameRate = pos; |
| mParams.setPreviewFrameRate(mPreviewFrameRates.get(mPreviewFrameRate)); |
| |
| log("Setting preview frame rate to " + ((TextView)view).getText()); |
| |
| mCamera.setParameters(mParams); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private View.OnClickListener mHDRToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (mState == CAMERA_TAKE_PICTURE) { |
| logE("Can't change preview state while taking picture!"); |
| return; |
| } |
| |
| if (mHDRToggle.isChecked()) { |
| log("Turning on HDR"); |
| mParams.setSceneMode(Camera.Parameters.SCENE_MODE_HDR); |
| } else { |
| log("Turning off HDR"); |
| mParams.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO); |
| } |
| mCamera.setParameters(mParams); |
| } |
| }; |
| |
| private View.OnClickListener mPreviewToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (mState == CAMERA_TAKE_PICTURE) { |
| logE("Can't change preview state while taking picture!"); |
| return; |
| } |
| if (mPreviewToggle.isChecked()) { |
| log("Starting preview"); |
| mCamera.startPreview(); |
| mState = CAMERA_PREVIEW; |
| enablePreviewOnlyControls(true); |
| } else { |
| log("Stopping preview"); |
| mCamera.stopPreview(); |
| mState = CAMERA_OPEN; |
| |
| enablePreviewOnlyControls(false); |
| } |
| } |
| }; |
| |
| private OnItemSelectedListener mAutofocusModeListener = |
| new OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mAfMode) return; |
| |
| mAfMode = pos; |
| String focusMode = mAfModes.get(mAfMode); |
| log("Setting focus mode to " + focusMode); |
| if (focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE || |
| focusMode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) { |
| mCamera.setAutoFocusMoveCallback(mAutofocusMoveCallback); |
| } |
| mParams.setFocusMode(focusMode); |
| |
| mCamera.setParameters(mParams); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> arg0) { |
| |
| } |
| }; |
| |
| private OnClickListener mAutofocusButtonListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| log("Triggering autofocus"); |
| mCamera.autoFocus(mAutofocusCallback); |
| } |
| }; |
| |
| private OnClickListener mCancelAutofocusButtonListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| log("Cancelling autofocus"); |
| mCamera.cancelAutoFocus(); |
| } |
| }; |
| |
| private Camera.AutoFocusCallback mAutofocusCallback = |
| new Camera.AutoFocusCallback() { |
| @Override |
| public void onAutoFocus(boolean success, Camera camera) { |
| log("Autofocus completed: " + (success ? "success" : "failure") ); |
| } |
| }; |
| |
| private Camera.AutoFocusMoveCallback mAutofocusMoveCallback = |
| new Camera.AutoFocusMoveCallback() { |
| @Override |
| public void onAutoFocusMoving(boolean start, Camera camera) { |
| log("Autofocus movement: " + (start ? "starting" : "stopped") ); |
| } |
| }; |
| |
| private OnItemSelectedListener mFlashModeListener = |
| new OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mFlashMode) return; |
| |
| mFlashMode = pos; |
| String flashMode = mFlashModes.get(mFlashMode); |
| log("Setting flash mode to " + flashMode); |
| mParams.setFlashMode(flashMode); |
| mCamera.setParameters(mParams); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> arg0) { |
| |
| } |
| }; |
| |
| |
| private AdapterView.OnItemSelectedListener mSnapshotSizeListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mSnapshotSize) return; |
| |
| mSnapshotSize = pos; |
| int width = mSnapshotSizes.get(mSnapshotSize).width; |
| int height = mSnapshotSizes.get(mSnapshotSize).height; |
| log("Setting snapshot size to " + width + " x " + height); |
| |
| mParams.setPictureSize(width, height); |
| |
| mCamera.setParameters(mParams); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private View.OnClickListener mTakePictureListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| log("Taking picture"); |
| if (mState == CAMERA_PREVIEW) { |
| mState = CAMERA_TAKE_PICTURE; |
| enablePreviewOnlyControls(false); |
| mPreviewToggle.setChecked(false); |
| |
| mCamera.takePicture(mShutterCb, mRawCb, mPostviewCb, mJpegCb); |
| } else { |
| logE("Can't take picture while not running preview!"); |
| } |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mCamcorderProfileListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos != mCamcorderProfile) { |
| log("Setting camcorder profile to " + ((TextView)view).getText()); |
| mCamcorderProfile = pos; |
| } |
| |
| // Additionally change video recording size to match |
| mVideoRecordSize = 0; // "default", in case it's not found |
| int width = mCamcorderProfiles.get(pos).videoFrameWidth; |
| int height = mCamcorderProfiles.get(pos).videoFrameHeight; |
| for (int i = 0; i < mVideoRecordSizes.size(); i++) { |
| Camera.Size s = mVideoRecordSizes.get(i); |
| if (width == s.width && height == s.height) { |
| mVideoRecordSize = i; |
| break; |
| } |
| } |
| log("Setting video record size to " + mVideoRecordSize); |
| mVideoRecordSizeSpinner.setSelection(mVideoRecordSize); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mVideoRecordSizeListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mVideoRecordSize) return; |
| |
| log("Setting video record size to " + ((TextView)view).getText()); |
| mVideoRecordSize = pos; |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mVideoFrameRateListener = |
| new AdapterView.OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mVideoFrameRate) return; |
| |
| log("Setting video frame rate to " + ((TextView)view).getText()); |
| mVideoFrameRate = pos; |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private View.OnClickListener mRecordToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (!mLockCameraToggle.isChecked()) { |
| logE("Re-lock camera before recording"); |
| return; |
| } |
| |
| mPreviewToggle.setEnabled(false); |
| if (mState == CAMERA_PREVIEW) { |
| startRecording(); |
| } else if (mState == CAMERA_RECORD) { |
| stopRecording(false); |
| } else { |
| logE("Can't toggle recording in current state!"); |
| } |
| mPreviewToggle.setEnabled(true); |
| } |
| }; |
| |
| private View.OnClickListener mRecordStabilizationToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| boolean on = ((ToggleButton) v).isChecked(); |
| mParams.setVideoStabilization(on); |
| |
| mCamera.setParameters(mParams); |
| } |
| }; |
| |
| private View.OnClickListener mRecordHintToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| boolean on = ((ToggleButton) v).isChecked(); |
| mParams.setRecordingHint(on); |
| |
| mCamera.setParameters(mParams); |
| } |
| }; |
| |
| private View.OnClickListener mLockCameraToggleListener = |
| new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| |
| if (mState == CAMERA_RECORD) { |
| logE("Stop recording before toggling lock"); |
| return; |
| } |
| |
| boolean on = ((ToggleButton) v).isChecked(); |
| |
| if (on) { |
| mCamera.lock(); |
| log("Locked camera"); |
| } else { |
| mCamera.unlock(); |
| log("Unlocked camera"); |
| } |
| } |
| }; |
| |
| private Camera.ShutterCallback mShutterCb = new Camera.ShutterCallback() { |
| @Override |
| public void onShutter() { |
| log("Shutter callback received"); |
| } |
| }; |
| |
| private Camera.PictureCallback mRawCb = new Camera.PictureCallback() { |
| @Override |
| public void onPictureTaken(byte[] data, Camera camera) { |
| log("Raw callback received"); |
| } |
| }; |
| |
| private Camera.PictureCallback mPostviewCb = new Camera.PictureCallback() { |
| @Override |
| public void onPictureTaken(byte[] data, Camera camera) { |
| log("Postview callback received"); |
| } |
| }; |
| |
| private Camera.PictureCallback mJpegCb = new Camera.PictureCallback() { |
| @Override |
| public void onPictureTaken(byte[] data, Camera camera) { |
| log("JPEG picture callback received"); |
| FragmentManager fm = getFragmentManager(); |
| SnapshotDialogFragment snapshotDialog = new SnapshotDialogFragment(); |
| |
| snapshotDialog.updateImage(data); |
| snapshotDialog.show(fm, "snapshot_dialog_fragment"); |
| |
| mPreviewToggle.setEnabled(true); |
| |
| mState = CAMERA_OPEN; |
| } |
| }; |
| |
| private AdapterView.OnItemSelectedListener mCallbackFormatListener = |
| new AdapterView.OnItemSelectedListener() { |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| mPreviewFormat = pos; |
| |
| log("Setting preview format to " + |
| mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); |
| |
| switch (mState) { |
| case CAMERA_UNINITIALIZED: |
| return; |
| case CAMERA_OPEN: |
| break; |
| case CAMERA_PREVIEW: |
| if (mCallbacksEnabled) { |
| log("Stopping preview and callbacks to switch formats"); |
| stopCallbacks(); |
| mCamera.stopPreview(); |
| } |
| break; |
| case CAMERA_RECORD: |
| logE("Can't update format while recording active"); |
| return; |
| } |
| |
| mParams.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); |
| mCamera.setParameters(mParams); |
| |
| if (mCallbacksEnabled) { |
| if (mState == CAMERA_PREVIEW) { |
| mCamera.startPreview(); |
| } |
| } |
| |
| configureCallbacks(mCallbackView.getWidth(), mCallbackView.getHeight()); |
| } |
| |
| public void onNothingSelected(AdapterView<?> parent) { |
| |
| } |
| }; |
| |
| private View.OnClickListener mCallbackToggleListener = |
| new View.OnClickListener() { |
| public void onClick(View v) { |
| if (mCallbacksEnabled) { |
| log("Disabling preview callbacks"); |
| stopCallbacks(); |
| mCallbacksEnabled = false; |
| resizePreview(); |
| mCallbackView.setVisibility(View.GONE); |
| |
| } else { |
| log("Enabling preview callbacks"); |
| mCallbacksEnabled = true; |
| resizePreview(); |
| mCallbackView.setVisibility(View.VISIBLE); |
| } |
| } |
| }; |
| |
| |
| // Internal methods |
| |
| void setUpCamera() { |
| if (mCameraId == NO_CAMERA_ID) return; |
| |
| log("Setting up camera " + mCameraId); |
| logIndent(1); |
| |
| if (mState < CAMERA_OPEN) { |
| log("Opening camera " + mCameraId); |
| |
| try { |
| mCamera = Camera.open(mCameraId); |
| } catch (RuntimeException e) { |
| logE("Exception opening camera: " + e.getMessage()); |
| resetCamera(); |
| mCameraSpinner.setSelection(0); |
| logIndent(-1); |
| return; |
| } |
| mState = CAMERA_OPEN; |
| } |
| |
| mCamera.setErrorCallback(this); |
| |
| setCameraDisplayOrientation(); |
| mParams = mCamera.getParameters(); |
| if (mParams != null) { |
| List<String> sceneModes = mParams.getSupportedSceneModes(); |
| for (String mode : sceneModes) { |
| if (Camera.Parameters.SCENE_MODE_HDR.equals(mode)){ |
| mHDRToggle.setEnabled(true); |
| } else { |
| mHDRToggle.setEnabled(false); |
| } |
| } |
| } |
| |
| // Set up preview size selection |
| |
| log("Configuring camera"); |
| logIndent(1); |
| |
| updatePreviewSizes(mParams); |
| updatePreviewFrameRate(mCameraId); |
| updatePreviewFormats(mParams); |
| updateAfModes(mParams); |
| updateFlashModes(mParams); |
| updateSnapshotSizes(mParams); |
| updateCamcorderProfile(mCameraId); |
| updateVideoRecordSize(mCameraId); |
| updateVideoFrameRate(mCameraId); |
| updateColorEffects(mParams); |
| |
| // Trigger updating video record size to match camcorder profile |
| mCamcorderProfileSpinner.setSelection(mCamcorderProfile); |
| |
| if (mParams.isVideoStabilizationSupported()) { |
| log("Video stabilization is supported"); |
| mRecordStabilizationToggle.setEnabled(true); |
| } else { |
| log("Video stabilization not supported"); |
| mRecordStabilizationToggle.setEnabled(false); |
| } |
| |
| if (mParams.isAutoExposureLockSupported()) { |
| log("Auto-Exposure locking is supported"); |
| mExposureLockToggle.setEnabled(true); |
| } else { |
| log("Auto-Exposure locking is not supported"); |
| mExposureLockToggle.setEnabled(false); |
| } |
| |
| if (mParams.isZoomSupported()) { |
| int maxZoom = mParams.getMaxZoom(); |
| mZoomSeekBar.setMax(maxZoom); |
| log("Zoom is supported, set max to " + maxZoom); |
| mZoomSeekBar.setEnabled(true); |
| } else { |
| log("Zoom is not supported"); |
| mZoomSeekBar.setEnabled(false); |
| } |
| |
| // Update parameters based on above updates |
| mCamera.setParameters(mParams); |
| |
| if (mPreviewHolder != null) { |
| log("Setting preview display"); |
| try { |
| mCamera.setPreviewDisplay(mPreviewHolder); |
| } catch(IOException e) { |
| Log.e(TAG, "Unable to set up preview!"); |
| } |
| } |
| |
| logIndent(-1); |
| |
| enableOpenOnlyControls(true); |
| |
| resizePreview(); |
| if (mPreviewToggle.isChecked()) { |
| log("Starting preview" ); |
| mCamera.startPreview(); |
| mState = CAMERA_PREVIEW; |
| } else { |
| mState = CAMERA_OPEN; |
| enablePreviewOnlyControls(false); |
| } |
| logIndent(-1); |
| } |
| |
| private void resetCamera() { |
| if (mState >= CAMERA_OPEN) { |
| log("Closing old camera"); |
| mCamera.release(); |
| } |
| mCamera = null; |
| mCameraId = NO_CAMERA_ID; |
| mState = CAMERA_UNINITIALIZED; |
| |
| enableOpenOnlyControls(false); |
| } |
| |
| private void updateAfModes(Parameters params) { |
| mAfModes = params.getSupportedFocusModes(); |
| |
| mAutofocusModeSpinner.setAdapter( |
| new ArrayAdapter<String>(this, R.layout.spinner_item, |
| mAfModes.toArray(new String[0]))); |
| |
| mAfMode = 0; |
| |
| params.setFocusMode(mAfModes.get(mAfMode)); |
| |
| log("Setting AF mode to " + mAfModes.get(mAfMode)); |
| } |
| |
| private void updateFlashModes(Parameters params) { |
| mFlashModes = params.getSupportedFlashModes(); |
| |
| if (mFlashModes != null) { |
| mFlashModeSpinnerLabel.setVisibility(View.VISIBLE); |
| mFlashModeSpinner.setVisibility(View.VISIBLE); |
| mFlashModeSpinner.setAdapter( |
| new ArrayAdapter<String>(this, R.layout.spinner_item, |
| mFlashModes.toArray(new String[0]))); |
| |
| mFlashMode = 0; |
| |
| params.setFlashMode(mFlashModes.get(mFlashMode)); |
| |
| log("Setting Flash mode to " + mFlashModes.get(mFlashMode)); |
| } else { |
| // this camera has no flash |
| mFlashModeSpinnerLabel.setVisibility(View.GONE); |
| mFlashModeSpinner.setVisibility(View.GONE); |
| } |
| } |
| |
| private View.OnClickListener mExposureLockToggleListener = |
| new View.OnClickListener() { |
| public void onClick(View v) { |
| boolean on = ((ToggleButton) v).isChecked(); |
| log("Auto-Exposure was " + mParams.getAutoExposureLock()); |
| mParams.setAutoExposureLock(on); |
| log("Auto-Exposure is now " + mParams.getAutoExposureLock()); |
| } |
| }; |
| |
| private final SeekBar.OnSeekBarChangeListener mZoomSeekBarListener = |
| new SeekBar.OnSeekBarChangeListener() { |
| @Override |
| public void onProgressChanged(SeekBar seekBar, int progress, |
| boolean fromUser) { |
| mZoom = progress; |
| mParams.setZoom(mZoom); |
| mCamera.setParameters(mParams); |
| } |
| @Override |
| public void onStartTrackingTouch(SeekBar seekBar) { } |
| @Override |
| public void onStopTrackingTouch(SeekBar seekBar) { |
| log("Zoom set to " + mZoom + " / " + mParams.getMaxZoom() + " (" + |
| ((float)(mParams.getZoomRatios().get(mZoom))/100) + "x)"); |
| } |
| }; |
| |
| private void updatePreviewSizes(Camera.Parameters params) { |
| mPreviewSizes = params.getSupportedPreviewSizes(); |
| |
| String[] availableSizeNames = new String[mPreviewSizes.size()]; |
| int i = 0; |
| for (Camera.Size previewSize: mPreviewSizes) { |
| availableSizeNames[i++] = |
| Integer.toString(previewSize.width) + " x " + |
| Integer.toString(previewSize.height); |
| } |
| mPreviewSizeSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, availableSizeNames)); |
| |
| mPreviewSize = 0; |
| |
| int width = mPreviewSizes.get(mPreviewSize).width; |
| int height = mPreviewSizes.get(mPreviewSize).height; |
| params.setPreviewSize(width, height); |
| log("Setting preview size to " + width + " x " + height); |
| } |
| |
| private void updatePreviewFrameRate(int cameraId) { |
| List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); |
| int defaultPreviewFrameRate = mParams.getPreviewFrameRate(); |
| |
| List<String> frameRateStrings = new ArrayList<String>(); |
| mPreviewFrameRates = new ArrayList<Integer>(); |
| |
| int currentIndex = 0; |
| for (Integer frameRate : frameRates) { |
| mPreviewFrameRates.add(frameRate); |
| if(frameRate == defaultPreviewFrameRate) { |
| frameRateStrings.add(frameRate.toString() + " (Default)"); |
| mPreviewFrameRate = currentIndex; |
| } else { |
| frameRateStrings.add(frameRate.toString()); |
| } |
| currentIndex++; |
| } |
| |
| String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); |
| mPreviewFrameRateSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, nameArray)); |
| |
| mPreviewFrameRateSpinner.setSelection(mPreviewFrameRate); |
| log("Setting preview frame rate to " + nameArray[mPreviewFrameRate]); |
| } |
| |
| private void updatePreviewFormats(Camera.Parameters params) { |
| mPreviewFormats = params.getSupportedPreviewFormats(); |
| |
| String[] availableFormatNames = new String[mPreviewFormats.size()]; |
| int i = 0; |
| for (Integer previewFormat: mPreviewFormats) { |
| availableFormatNames[i++] = mFormatNames.get(previewFormat); |
| } |
| mCallbackFormatSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, availableFormatNames)); |
| |
| mPreviewFormat = 0; |
| mCallbacksEnabled = false; |
| mCallbackToggle.setChecked(false); |
| mCallbackView.setVisibility(View.GONE); |
| |
| params.setPreviewFormat(mPreviewFormats.get(mPreviewFormat)); |
| log("Setting preview format to " + |
| mFormatNames.get(mPreviewFormats.get(mPreviewFormat))); |
| } |
| |
| private void updateSnapshotSizes(Camera.Parameters params) { |
| String[] availableSizeNames; |
| mSnapshotSizes = params.getSupportedPictureSizes(); |
| |
| availableSizeNames = new String[mSnapshotSizes.size()]; |
| int i = 0; |
| for (Camera.Size snapshotSize : mSnapshotSizes) { |
| availableSizeNames[i++] = |
| Integer.toString(snapshotSize.width) + " x " + |
| Integer.toString(snapshotSize.height); |
| } |
| mSnapshotSizeSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, availableSizeNames)); |
| |
| mSnapshotSize = 0; |
| |
| int snapshotWidth = mSnapshotSizes.get(mSnapshotSize).width; |
| int snapshotHeight = mSnapshotSizes.get(mSnapshotSize).height; |
| params.setPictureSize(snapshotWidth, snapshotHeight); |
| log("Setting snapshot size to " + snapshotWidth + " x " + snapshotHeight); |
| } |
| |
| private void updateCamcorderProfile(int cameraId) { |
| // Have to query all of these individually, |
| final int PROFILES[] = new int[] { |
| CamcorderProfile.QUALITY_2160P, |
| CamcorderProfile.QUALITY_1080P, |
| CamcorderProfile.QUALITY_480P, |
| CamcorderProfile.QUALITY_720P, |
| CamcorderProfile.QUALITY_CIF, |
| CamcorderProfile.QUALITY_HIGH, |
| CamcorderProfile.QUALITY_LOW, |
| CamcorderProfile.QUALITY_QCIF, |
| CamcorderProfile.QUALITY_QVGA, |
| CamcorderProfile.QUALITY_TIME_LAPSE_2160P, |
| CamcorderProfile.QUALITY_TIME_LAPSE_1080P, |
| CamcorderProfile.QUALITY_TIME_LAPSE_480P, |
| CamcorderProfile.QUALITY_TIME_LAPSE_720P, |
| CamcorderProfile.QUALITY_TIME_LAPSE_CIF, |
| CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, |
| CamcorderProfile.QUALITY_TIME_LAPSE_LOW, |
| CamcorderProfile.QUALITY_TIME_LAPSE_QCIF, |
| CamcorderProfile.QUALITY_TIME_LAPSE_QVGA |
| }; |
| |
| final String PROFILE_NAMES[] = new String[] { |
| "2160P", |
| "1080P", |
| "480P", |
| "720P", |
| "CIF", |
| "HIGH", |
| "LOW", |
| "QCIF", |
| "QVGA", |
| "TIME_LAPSE_2160P", |
| "TIME_LAPSE_1080P", |
| "TIME_LAPSE_480P", |
| "TIME_LAPSE_720P", |
| "TIME_LAPSE_CIF", |
| "TIME_LAPSE_HIGH", |
| "TIME_LAPSE_LOW", |
| "TIME_LAPSE_QCIF", |
| "TIME_LAPSE_QVGA" |
| }; |
| |
| List<String> availableCamcorderProfileNames = new ArrayList<String>(); |
| mCamcorderProfiles = new ArrayList<CamcorderProfile>(); |
| |
| for (int i = 0; i < PROFILES.length; i++) { |
| if (CamcorderProfile.hasProfile(cameraId, PROFILES[i])) { |
| availableCamcorderProfileNames.add(PROFILE_NAMES[i]); |
| mCamcorderProfiles.add(CamcorderProfile.get(cameraId, PROFILES[i])); |
| } |
| } |
| String[] nameArray = (String[])availableCamcorderProfileNames.toArray(new String[0]); |
| mCamcorderProfileSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, nameArray)); |
| |
| mCamcorderProfile = 0; |
| log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); |
| |
| } |
| |
| private void updateVideoRecordSize(int cameraId) { |
| List<Camera.Size> videoSizes = mParams.getSupportedVideoSizes(); |
| if (videoSizes == null) { // TODO: surface this to the user |
| log("Failed to get video size list, using preview sizes instead"); |
| videoSizes = mParams.getSupportedPreviewSizes(); |
| } |
| |
| List<String> availableVideoRecordSizes = new ArrayList<String>(); |
| mVideoRecordSizes = new ArrayList<Camera.Size>(); |
| |
| availableVideoRecordSizes.add("Default"); |
| mVideoRecordSizes.add(mCamera.new Size(0,0)); |
| |
| for (Camera.Size s : videoSizes) { |
| availableVideoRecordSizes.add(s.width + "x" + s.height); |
| mVideoRecordSizes.add(s); |
| } |
| String[] nameArray = (String[])availableVideoRecordSizes.toArray(new String[0]); |
| mVideoRecordSizeSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, nameArray)); |
| |
| mVideoRecordSize = 0; |
| log("Setting video record profile to " + nameArray[mVideoRecordSize]); |
| } |
| |
| private void updateVideoFrameRate(int cameraId) { |
| // Use preview framerates as video framerates |
| List<Integer> frameRates = mParams.getSupportedPreviewFrameRates(); |
| |
| List<String> frameRateStrings = new ArrayList<String>(); |
| mVideoFrameRates = new ArrayList<Integer>(); |
| |
| frameRateStrings.add("Default"); |
| mVideoFrameRates.add(0); |
| |
| for (Integer frameRate : frameRates) { |
| frameRateStrings.add(frameRate.toString()); |
| mVideoFrameRates.add(frameRate); |
| } |
| String[] nameArray = (String[])frameRateStrings.toArray(new String[0]); |
| mVideoFrameRateSpinner.setAdapter( |
| new ArrayAdapter<String>( |
| this, R.layout.spinner_item, nameArray)); |
| |
| mVideoFrameRate = 0; |
| log("Setting recording frame rate to " + nameArray[mVideoFrameRate]); |
| } |
| |
| void resizePreview() { |
| // Reset preview layout parameters, to trigger layout pass |
| // This will eventually call layoutPreview below |
| Resources res = getResources(); |
| mPreviewView.setLayoutParams( |
| new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, |
| mCallbacksEnabled ? |
| res.getInteger(R.integer.preview_with_callback_weight): |
| res.getInteger(R.integer.preview_only_weight) )); |
| } |
| |
| void layoutPreview() { |
| int width = mPreviewSizes.get(mPreviewSize).width; |
| int height = mPreviewSizes.get(mPreviewSize).height; |
| float previewAspect = ((float) width) / height; |
| |
| int viewHeight = mPreviewView.getHeight(); |
| int viewWidth = mPreviewView.getWidth(); |
| float viewAspect = ((float) viewWidth) / viewHeight; |
| if ( previewAspect > viewAspect) { |
| viewHeight = (int) (viewWidth / previewAspect); |
| } else { |
| viewWidth = (int) (viewHeight * previewAspect); |
| } |
| mPreviewView.setLayoutParams( |
| new LayoutParams(viewWidth, viewHeight)); |
| |
| if (mCallbacksEnabled) { |
| int callbackHeight = mCallbackView.getHeight(); |
| int callbackWidth = mCallbackView.getWidth(); |
| float callbackAspect = ((float) callbackWidth) / callbackHeight; |
| if ( previewAspect > callbackAspect) { |
| callbackHeight = (int) (callbackWidth / previewAspect); |
| } else { |
| callbackWidth = (int) (callbackHeight * previewAspect); |
| } |
| mCallbackView.setLayoutParams( |
| new LayoutParams(callbackWidth, callbackHeight)); |
| configureCallbacks(callbackWidth, callbackHeight); |
| } |
| } |
| |
| |
| private void configureCallbacks(int callbackWidth, int callbackHeight) { |
| if (mState >= CAMERA_OPEN && mCallbacksEnabled) { |
| mCamera.setPreviewCallbackWithBuffer(null); |
| int width = mPreviewSizes.get(mPreviewSize).width; |
| int height = mPreviewSizes.get(mPreviewSize).height; |
| int format = mPreviewFormats.get(mPreviewFormat); |
| |
| mCallbackProcessor = new CallbackProcessor(width, height, format, |
| getResources(), mCallbackView, |
| callbackWidth, callbackHeight, mRS); |
| |
| int size = getCallbackBufferSize(width, height, format); |
| log("Configuring callbacks:" + width + " x " + height + |
| " , format " + format); |
| for (int i = 0; i < CALLBACK_BUFFER_COUNT; i++) { |
| mCamera.addCallbackBuffer(new byte[size]); |
| } |
| mCamera.setPreviewCallbackWithBuffer(this); |
| } |
| mLastCallbackTimestamp = -1; |
| mCallbackFrameCount = 0; |
| mCallbackAvgFrameDuration = 30; |
| } |
| |
| private void stopCallbacks() { |
| if (mState >= CAMERA_OPEN) { |
| mCamera.setPreviewCallbackWithBuffer(null); |
| if (mCallbackProcessor != null) { |
| if (!mCallbackProcessor.stop()) { |
| logE("Can't stop preview callback processing!"); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onPreviewFrame(byte[] data, Camera camera) { |
| long timestamp = SystemClock.elapsedRealtime(); |
| if (mLastCallbackTimestamp != -1) { |
| long frameDuration = timestamp - mLastCallbackTimestamp; |
| mCallbackAvgFrameDuration = |
| mCallbackAvgFrameDuration * MEAN_FPS_HISTORY_COEFF + |
| frameDuration * MEAN_FPS_MEASUREMENT_COEFF; |
| } |
| mLastCallbackTimestamp = timestamp; |
| if (mState < CAMERA_PREVIEW || !mCallbacksEnabled) { |
| mCamera.addCallbackBuffer(data); |
| return; |
| } |
| mCallbackFrameCount++; |
| if (mCallbackFrameCount % FPS_REPORTING_PERIOD == 0) { |
| log("Got " + FPS_REPORTING_PERIOD + " callback frames, fps " |
| + 1e3/mCallbackAvgFrameDuration); |
| } |
| mCallbackProcessor.displayCallback(data); |
| |
| mCamera.addCallbackBuffer(data); |
| } |
| |
| @Override |
| public void onError(int error, Camera camera) { |
| String errorName; |
| switch (error) { |
| case Camera.CAMERA_ERROR_SERVER_DIED: |
| errorName = "SERVER_DIED"; |
| break; |
| case Camera.CAMERA_ERROR_UNKNOWN: |
| errorName = "UNKNOWN"; |
| break; |
| default: |
| errorName = "?"; |
| break; |
| } |
| logE("Camera error received: " + errorName + " (" + error + ")" ); |
| logE("Shutting down camera"); |
| resetCamera(); |
| mCameraSpinner.setSelection(0); |
| } |
| |
| static final int MEDIA_TYPE_IMAGE = 0; |
| static final int MEDIA_TYPE_VIDEO = 1; |
| @SuppressLint("SimpleDateFormat") |
| File getOutputMediaFile(int type){ |
| // To be safe, you should check that the SDCard is mounted |
| // using Environment.getExternalStorageState() before doing this. |
| |
| String state = Environment.getExternalStorageState(); |
| if (!Environment.MEDIA_MOUNTED.equals(state)) { |
| return null; |
| } |
| |
| File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( |
| Environment.DIRECTORY_DCIM), "TestingCamera"); |
| // 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()){ |
| logE("Failed to create directory for pictures/video"); |
| return null; |
| } |
| } |
| |
| // Create a media file name |
| String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").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; |
| } |
| |
| void notifyMediaScannerOfFile(File newFile, |
| final MediaScannerConnection.OnScanCompletedListener listener) { |
| final Handler h = new Handler(); |
| MediaScannerConnection.scanFile(this, |
| new String[] { newFile.toString() }, |
| null, |
| new MediaScannerConnection.OnScanCompletedListener() { |
| @Override |
| public void onScanCompleted(final String path, final Uri uri) { |
| h.post(new Runnable() { |
| @Override |
| public void run() { |
| log("MediaScanner notified: " + |
| path + " -> " + uri); |
| if (listener != null) |
| listener.onScanCompleted(path, uri); |
| } |
| }); |
| } |
| }); |
| } |
| |
| private void deleteFile(File badFile) { |
| if (badFile.exists()) { |
| boolean success = badFile.delete(); |
| if (success) log("Deleted file " + badFile.toString()); |
| else log("Unable to delete file " + badFile.toString()); |
| } |
| } |
| |
| private void startRecording() { |
| log("Starting recording"); |
| logIndent(1); |
| log("Configuring MediaRecoder"); |
| |
| mRecordHandoffCheckBox.setEnabled(false); |
| if (mRecordHandoffCheckBox.isChecked()) { |
| mCamera.release(); |
| } else { |
| mCamera.unlock(); |
| } |
| |
| if (mRecorder != null) { |
| mRecorder.release(); |
| } |
| |
| mRecorder = new MediaRecorder(); |
| mRecorder.setOnErrorListener(mRecordingErrorListener); |
| mRecorder.setOnInfoListener(mRecordingInfoListener); |
| if (!mRecordHandoffCheckBox.isChecked()) { |
| mRecorder.setCamera(mCamera); |
| } |
| mRecorder.setPreviewDisplay(mPreviewHolder.getSurface()); |
| |
| mRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); |
| mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); |
| mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile)); |
| Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize); |
| if (videoRecordSize.width > 0 && videoRecordSize.height > 0) { |
| mRecorder.setVideoSize(videoRecordSize.width, videoRecordSize.height); |
| } |
| if (mVideoFrameRates.get(mVideoFrameRate) > 0) { |
| mRecorder.setVideoFrameRate(mVideoFrameRates.get(mVideoFrameRate)); |
| } |
| File outputFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); |
| log("File name:" + outputFile.toString()); |
| mRecorder.setOutputFile(outputFile.toString()); |
| |
| boolean ready = false; |
| log("Preparing MediaRecorder"); |
| try { |
| mRecorder.prepare(); |
| ready = true; |
| } catch (Exception e) { |
| StringWriter writer = new StringWriter(); |
| e.printStackTrace(new PrintWriter(writer)); |
| logE("Exception preparing MediaRecorder:\n" + writer.toString()); |
| } |
| |
| if (ready) { |
| try { |
| log("Starting MediaRecorder"); |
| mRecorder.start(); |
| mState = CAMERA_RECORD; |
| log("Recording active"); |
| mRecordingFile = outputFile; |
| } catch (Exception e) { |
| StringWriter writer = new StringWriter(); |
| e.printStackTrace(new PrintWriter(writer)); |
| logE("Exception starting MediaRecorder:\n" + writer.toString()); |
| ready = false; |
| } |
| } |
| |
| if (!ready) { |
| mRecordToggle.setChecked(false); |
| mRecordHandoffCheckBox.setEnabled(true); |
| |
| if (mRecordHandoffCheckBox.isChecked()) { |
| mState = CAMERA_UNINITIALIZED; |
| setUpCamera(); |
| } |
| } |
| logIndent(-1); |
| } |
| |
| private MediaRecorder.OnErrorListener mRecordingErrorListener = |
| new MediaRecorder.OnErrorListener() { |
| @Override |
| public void onError(MediaRecorder mr, int what, int extra) { |
| logE("MediaRecorder reports error: " + what + ", extra " |
| + extra); |
| if (mState == CAMERA_RECORD) { |
| stopRecording(true); |
| } |
| } |
| }; |
| |
| private MediaRecorder.OnInfoListener mRecordingInfoListener = |
| new MediaRecorder.OnInfoListener() { |
| @Override |
| public void onInfo(MediaRecorder mr, int what, int extra) { |
| log("MediaRecorder reports info: " + what + ", extra " |
| + extra); |
| } |
| }; |
| |
| private void stopRecording(boolean error) { |
| log("Stopping recording"); |
| mRecordHandoffCheckBox.setEnabled(true); |
| mRecordToggle.setChecked(false); |
| if (mRecorder != null) { |
| try { |
| mRecorder.stop(); |
| } catch (RuntimeException e) { |
| // this can happen if there were no frames received by recorder |
| logE("Could not create output file"); |
| error = true; |
| } |
| |
| if (mRecordHandoffCheckBox.isChecked()) { |
| mState = CAMERA_UNINITIALIZED; |
| setUpCamera(); |
| } else { |
| mCamera.lock(); |
| mState = CAMERA_PREVIEW; |
| } |
| |
| if (!error) { |
| notifyMediaScannerOfFile(mRecordingFile, null); |
| } else { |
| deleteFile(mRecordingFile); |
| } |
| mRecordingFile = null; |
| } else { |
| logE("Recorder is unexpectedly null!"); |
| } |
| } |
| |
| static int getCallbackBufferSize(int width, int height, int format) { |
| int size = -1; |
| switch (format) { |
| case ImageFormat.NV21: |
| size = width * height * 3 / 2; |
| break; |
| case ImageFormat.YV12: |
| int y_stride = (int) (Math.ceil( width / 16.) * 16); |
| int y_size = y_stride * height; |
| int c_stride = (int) (Math.ceil(y_stride / 32.) * 16); |
| int c_size = c_stride * height/2; |
| size = y_size + c_size * 2; |
| break; |
| case ImageFormat.NV16: |
| case ImageFormat.RGB_565: |
| case ImageFormat.YUY2: |
| size = 2 * width * height; |
| break; |
| case ImageFormat.JPEG: |
| Log.e(TAG, "JPEG callback buffers not supported!"); |
| size = 0; |
| break; |
| case ImageFormat.UNKNOWN: |
| Log.e(TAG, "Unknown-format callback buffers not supported!"); |
| size = 0; |
| break; |
| } |
| return size; |
| } |
| |
| private OnItemSelectedListener mColorEffectListener = |
| new OnItemSelectedListener() { |
| @Override |
| public void onItemSelected(AdapterView<?> parent, |
| View view, int pos, long id) { |
| if (pos == mColorEffect) return; |
| |
| mColorEffect = pos; |
| String colorEffect = mColorEffects.get(mColorEffect); |
| log("Setting color effect to " + colorEffect); |
| mParams.setColorEffect(colorEffect); |
| mCamera.setParameters(mParams); |
| } |
| |
| @Override |
| public void onNothingSelected(AdapterView<?> arg0) { |
| } |
| }; |
| |
| private void updateColorEffects(Parameters params) { |
| mColorEffects = params.getSupportedColorEffects(); |
| if (mColorEffects != null) { |
| mColorEffectSpinnerLabel.setVisibility(View.VISIBLE); |
| mColorEffectSpinner.setVisibility(View.VISIBLE); |
| mColorEffectSpinner.setAdapter( |
| new ArrayAdapter<String>(this, R.layout.spinner_item, |
| mColorEffects.toArray(new String[0]))); |
| mColorEffect = 0; |
| params.setColorEffect(mColorEffects.get(mColorEffect)); |
| log("Setting Color Effect to " + mColorEffects.get(mColorEffect)); |
| } else { |
| mColorEffectSpinnerLabel.setVisibility(View.GONE); |
| mColorEffectSpinner.setVisibility(View.GONE); |
| } |
| } |
| |
| private int mLogIndentLevel = 0; |
| private String mLogIndent = "\t"; |
| /** Increment or decrement log indentation level */ |
| synchronized void logIndent(int delta) { |
| mLogIndentLevel += delta; |
| if (mLogIndentLevel < 0) mLogIndentLevel = 0; |
| char[] mLogIndentArray = new char[mLogIndentLevel + 1]; |
| for (int i = -1; i < mLogIndentLevel; i++) { |
| mLogIndentArray[i + 1] = '\t'; |
| } |
| mLogIndent = new String(mLogIndentArray); |
| } |
| |
| @SuppressLint("SimpleDateFormat") |
| SimpleDateFormat mDateFormatter = new SimpleDateFormat("HH:mm:ss.SSS"); |
| /** Log both to log text view and to device logcat */ |
| void log(String logLine) { |
| Log.d(TAG, logLine); |
| logAndScrollToBottom(logLine, mLogIndent); |
| } |
| |
| void logE(String logLine) { |
| Log.e(TAG, logLine); |
| logAndScrollToBottom(logLine, mLogIndent + "!!! "); |
| } |
| |
| synchronized private void logAndScrollToBottom(String logLine, String logIndent) { |
| StringBuffer logEntry = new StringBuffer(32); |
| logEntry.append("\n").append(mDateFormatter.format(new Date())).append(logIndent); |
| logEntry.append(logLine); |
| mLogView.append(logEntry); |
| final Layout layout = mLogView.getLayout(); |
| if (layout != null){ |
| int scrollDelta = layout.getLineBottom(mLogView.getLineCount() - 1) |
| - mLogView.getScrollY() - mLogView.getHeight(); |
| if(scrollDelta > 0) { |
| mLogView.scrollBy(0, scrollDelta); |
| } |
| } |
| } |
| } |