blob: f796dee331544f1b06aeb4a94454f4e3506e2a3e [file] [log] [blame]
/*
* 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.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.FragmentManager;
import android.content.pm.PackageManager;
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.OrientationEventListener;
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;
SnapshotDialogFragment mSnapshotDialog = null;
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";
private static final int PERMISSIONS_REQUEST_CAMERA = 1;
private static final int PERMISSIONS_REQUEST_RECORDING = 2;
static final int PERMISSIONS_REQUEST_SNAPSHOT = 3;
private OrientationEventHandler mOrientationHandler;
/** 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);
mOrientationHandler = new OrientationEventHandler(this);
}
private static class OrientationEventHandler extends OrientationEventListener {
private TestingCamera mActivity;
private int mCurrentRotation = -1;
OrientationEventHandler(TestingCamera activity) {
super(activity);
mActivity = activity;
}
@Override
public void onOrientationChanged(int orientation) {
if (mActivity != null) {
int rotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
if (mCurrentRotation != rotation) {
mCurrentRotation = rotation;
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
mActivity.setCameraDisplayOrientation();
mActivity.resizePreview();
}
});
}
}
}
}
@Override
public void onResume() {
super.onResume();
log("onResume: Setting up");
setUpCamera();
mOrientationHandler.enable();
}
@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;
}
mOrientationHandler.disable();
}
@Override
public void onRequestPermissionsResult (int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == PERMISSIONS_REQUEST_CAMERA) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
log("Camera permission granted");
setUpCamera();
} else {
log("Camera permission denied, can't do anything");
finish();
}
} else if (requestCode == PERMISSIONS_REQUEST_RECORDING) {
mRecordToggle.setChecked(false);
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
log("Recording permission " + permissions[i] + " denied");
return;
}
log("Recording permissions granted");
setUpCamera();
}
} else if (requestCode == PERMISSIONS_REQUEST_SNAPSHOT) {
if (mSnapshotDialog != null) {
mSnapshotDialog.onRequestPermissionsResult(requestCode, permissions,
grantResults);
}
}
}
/** 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 || mState == CAMERA_UNINITIALIZED) {
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();
mSnapshotDialog = new SnapshotDialogFragment();
mSnapshotDialog.updateImage(data);
mSnapshotDialog.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);
if (checkSelfPermission(Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
log("Requested camera permission");
requestPermissions(new String[] {Manifest.permission.CAMERA},
PERMISSIONS_REQUEST_CAMERA);
return;
}
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();
mHDRToggle.setEnabled(false);
if (mParams != null) {
List<String> sceneModes = mParams.getSupportedSceneModes();
if (sceneModes != null) {
for (String mode : sceneModes) {
if (Camera.Parameters.SCENE_MODE_HDR.equals(mode)){
mHDRToggle.setEnabled(true);
break;
}
}
} else {
Log.i(TAG, "Supported scene modes is null");
}
}
// 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
if (mCamcorderProfile >= 0) {
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));
if (availableCamcorderProfileNames.size() == 0) {
log("Camera " + cameraId + " doesn't support camcorder profile");
mCamcorderProfile = -1;
return;
}
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 rotation = getWindowManager().getDefaultDisplay().getRotation();
int width = mPreviewSizes.get(mPreviewSize).width;
int height = mPreviewSizes.get(mPreviewSize).height;
switch (rotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
// Portrait
// Switch the preview size so that the longer edge aligns with the taller
// dimension.
if (width > height) {
int tmp = height;
height = width;
width = tmp;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
// Landscape
// Possibly somewhat unlikely case but we should try to handle it too.
if (height > width) {
int tmp = height;
height = width;
width = tmp;
}
break;
}
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));
log("Setting layout params viewWidth: " + viewWidth + " viewHeight: " + viewHeight +
" display rotation: " + rotation);
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 static final int BIT_RATE_1080P = 16000000;
private static final int BIT_RATE_MIN = 64000;
private static final int BIT_RATE_MAX = 40000000;
private int getVideoBitRate(Camera.Size sz) {
int rate = BIT_RATE_1080P;
float scaleFactor = sz.height * sz.width / (float)(1920 * 1080);
rate = (int)(rate * scaleFactor);
// Clamp to the MIN, MAX range.
return Math.max(BIT_RATE_MIN, Math.min(BIT_RATE_MAX, rate));
}
private void startRecording() {
log("Starting recording");
if ((checkSelfPermission(Manifest.permission.RECORD_AUDIO)
!= PackageManager.PERMISSION_GRANTED)
|| (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED)) {
log("Requesting recording permissions (audio, storage)");
requestPermissions(new String[] {
Manifest.permission.RECORD_AUDIO,
Manifest.permission.WRITE_EXTERNAL_STORAGE},
PERMISSIONS_REQUEST_RECORDING);
return;
}
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);
Camera.Size videoRecordSize = mVideoRecordSizes.get(mVideoRecordSize);
if (mCamcorderProfile >= 0) {
mRecorder.setProfile(mCamcorderProfiles.get(mCamcorderProfile));
} else {
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setVideoEncodingBitRate(getVideoBitRate(videoRecordSize));
mRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
}
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);
}
}
}
}