/* * 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.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 mOpenOnlyControls = new HashSet(); private Set mPreviewOnlyControls = new HashSet(); private SparseArray mFormatNames; /** Camera state */ private int mCameraId; private Camera mCamera; private Camera.Parameters mParams; private List mPreviewSizes; private int mPreviewSize = 0; private List mPreviewFrameRates; private int mPreviewFrameRate = 0; private List mPreviewFormats; private int mPreviewFormat = 0; private List mAfModes; private int mAfMode = 0; private List mFlashModes; private int mFlashMode = 0; private List mSnapshotSizes; private int mSnapshotSize = 0; private List mCamcorderProfiles; private int mCamcorderProfile = 0; private List mVideoRecordSizes; private int mVideoRecordSize = 0; private List mVideoFrameRates; private int mVideoFrameRate = 0; private List 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; /** 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(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(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"); 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; } } @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) { 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 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 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(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(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( 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 frameRates = mParams.getSupportedPreviewFrameRates(); int defaultPreviewFrameRate = mParams.getPreviewFrameRate(); List frameRateStrings = new ArrayList(); mPreviewFrameRates = new ArrayList(); 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( 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( 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( 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 availableCamcorderProfileNames = new ArrayList(); mCamcorderProfiles = new ArrayList(); 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( this, R.layout.spinner_item, nameArray)); mCamcorderProfile = 0; log("Setting camcorder profile to " + nameArray[mCamcorderProfile]); } private void updateVideoRecordSize(int cameraId) { List 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 availableVideoRecordSizes = new ArrayList(); mVideoRecordSizes = new ArrayList(); 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( 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 frameRates = mParams.getSupportedPreviewFrameRates(); List frameRateStrings = new ArrayList(); mVideoFrameRates = new ArrayList(); 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( 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"); 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); 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(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); } } } }