Home | History | Annotate | Download | only in testingcamera2
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.testingcamera2;
     18 
     19 import android.app.Activity;
     20 import android.content.res.Configuration;
     21 import android.graphics.Bitmap;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.ImageFormat;
     24 import android.hardware.camera2.CameraCharacteristics;
     25 import android.hardware.camera2.CameraDevice;
     26 import android.hardware.camera2.CaptureFailure;
     27 import android.hardware.camera2.CaptureRequest;
     28 import android.hardware.camera2.CaptureResult;
     29 import android.media.Image;
     30 import android.os.AsyncTask;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.util.Log;
     34 import android.view.SurfaceHolder;
     35 import android.view.SurfaceView;
     36 import android.view.View;
     37 import android.view.ViewGroup.LayoutParams;
     38 import android.view.WindowManager;
     39 import android.widget.Button;
     40 import android.widget.ImageView;
     41 import android.widget.SeekBar;
     42 import android.widget.SeekBar.OnSeekBarChangeListener;
     43 import android.widget.TextView;
     44 import android.widget.ToggleButton;
     45 
     46 import java.nio.ByteBuffer;
     47 import java.util.HashSet;
     48 import java.util.Set;
     49 
     50 public class TestingCamera2 extends Activity implements SurfaceHolder.Callback {
     51 
     52     private static final String TAG = "TestingCamera2";
     53     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     54     private CameraOps mCameraOps;
     55     private static final int mSeekBarMax = 100;
     56     private static final long MAX_EXPOSURE = 200000000L; // 200ms
     57     private static final long MIN_EXPOSURE = 100000L; // 100us
     58     private static final long MAX_FRAME_DURATION = 1000000000L; // 1s
     59     // Manual control change step size
     60     private static final int STEP_SIZE = 100;
     61     // Min and max sensitivity ISO values
     62     private static final int MIN_SENSITIVITY = 100;
     63     private static final int MAX_SENSITIVITY = 1600;
     64     private static final int ORIENTATION_UNINITIALIZED = -1;
     65 
     66     private int mLastOrientation = ORIENTATION_UNINITIALIZED;
     67     private SurfaceView mPreviewView;
     68     private ImageView mStillView;
     69 
     70     private SurfaceHolder mCurrentPreviewHolder = null;
     71 
     72     private Button mInfoButton;
     73 
     74     private SeekBar mSensitivityBar;
     75     private SeekBar mExposureBar;
     76     private SeekBar mFrameDurationBar;
     77 
     78     private TextView mSensitivityInfoView;
     79     private TextView mExposureInfoView;
     80     private TextView mFrameDurationInfoView;
     81     private TextView mCaptureResultView;
     82     private ToggleButton mRecordingToggle;
     83     private ToggleButton mManualCtrlToggle;
     84 
     85     private CameraControls mCameraControl = null;
     86     private final Set<View> mManualControls = new HashSet<View>();
     87 
     88     Handler mMainHandler;
     89 
     90     @Override
     91     public void onCreate(Bundle savedInstanceState) {
     92         super.onCreate(savedInstanceState);
     93 
     94         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
     95                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
     96 
     97         setContentView(R.layout.main);
     98 
     99         mPreviewView = (SurfaceView) findViewById(R.id.preview_view);
    100         mPreviewView.getHolder().addCallback(this);
    101 
    102         mStillView = (ImageView) findViewById(R.id.still_view);
    103 
    104         mInfoButton  = (Button) findViewById(R.id.info_button);
    105         mInfoButton.setOnClickListener(mInfoButtonListener);
    106         mRecordingToggle = (ToggleButton) findViewById(R.id.start_recording);
    107         mRecordingToggle.setOnClickListener(mRecordingToggleListener);
    108 
    109         mManualCtrlToggle = (ToggleButton) findViewById(R.id.manual_control);
    110         mManualCtrlToggle.setOnClickListener(mControlToggleListener);
    111 
    112         mSensitivityBar = (SeekBar) findViewById(R.id.sensitivity_seekbar);
    113         mSensitivityBar.setOnSeekBarChangeListener(mSensitivitySeekBarListener);
    114         mSensitivityBar.setMax(mSeekBarMax);
    115         mManualControls.add(mSensitivityBar);
    116 
    117         mExposureBar = (SeekBar) findViewById(R.id.exposure_time_seekbar);
    118         mExposureBar.setOnSeekBarChangeListener(mExposureSeekBarListener);
    119         mExposureBar.setMax(mSeekBarMax);
    120         mManualControls.add(mExposureBar);
    121 
    122         mFrameDurationBar = (SeekBar) findViewById(R.id.frame_duration_seekbar);
    123         mFrameDurationBar.setOnSeekBarChangeListener(mFrameDurationSeekBarListener);
    124         mFrameDurationBar.setMax(mSeekBarMax);
    125         mManualControls.add(mFrameDurationBar);
    126 
    127         mSensitivityInfoView = (TextView) findViewById(R.id.sensitivity_bar_label);
    128         mExposureInfoView = (TextView) findViewById(R.id.exposure_time_bar_label);
    129         mFrameDurationInfoView = (TextView) findViewById(R.id.frame_duration_bar_label);
    130         mCaptureResultView = (TextView) findViewById(R.id.capture_result_info_label);
    131 
    132         enableManualControls(false);
    133         mCameraControl = new CameraControls();
    134 
    135         // Get UI handler
    136         mMainHandler = new Handler();
    137 
    138         try {
    139             mCameraOps = CameraOps.create(this);
    140         } catch(ApiFailureException e) {
    141             logException("Cannot create camera ops!",e);
    142         }
    143 
    144         // Process the initial configuration (for i.e. initial orientation)
    145         // We need this because #onConfigurationChanged doesn't get called when the app launches
    146         maybeUpdateConfiguration(getResources().getConfiguration());
    147     }
    148 
    149     @Override
    150     public void onResume() {
    151         super.onResume();
    152         try {
    153             if (VERBOSE) Log.v(TAG, String.format("onResume"));
    154 
    155             mCameraOps.minimalPreviewConfig(mPreviewView.getHolder());
    156             mCurrentPreviewHolder = mPreviewView.getHolder();
    157         } catch (ApiFailureException e) {
    158             logException("Can't configure preview surface: ",e);
    159         }
    160     }
    161 
    162     @Override
    163     public void onPause() {
    164         super.onPause();
    165         try {
    166             if (VERBOSE) Log.v(TAG, String.format("onPause"));
    167 
    168             mCameraOps.closeDevice();
    169         } catch (ApiFailureException e) {
    170             logException("Can't close device: ",e);
    171         }
    172         mCurrentPreviewHolder = null;
    173     }
    174 
    175     @Override
    176     public void onConfigurationChanged(Configuration newConfig) {
    177         super.onConfigurationChanged(newConfig);
    178 
    179         if (VERBOSE) {
    180             Log.v(TAG, String.format("onConfiguredChanged: orientation %x",
    181                     newConfig.orientation));
    182         }
    183 
    184         maybeUpdateConfiguration(newConfig);
    185     }
    186 
    187     private void maybeUpdateConfiguration(Configuration newConfig) {
    188         if (VERBOSE) {
    189             Log.v(TAG, String.format("handleConfiguration: orientation %x",
    190                     newConfig.orientation));
    191         }
    192 
    193         if (mLastOrientation != newConfig.orientation) {
    194             mLastOrientation = newConfig.orientation;
    195             updatePreviewOrientation();
    196         }
    197     }
    198 
    199     private void updatePreviewOrientation() {
    200         LayoutParams params = mPreviewView.getLayoutParams();
    201         int width = params.width;
    202         int height = params.height;
    203 
    204         if (VERBOSE) {
    205             Log.v(TAG, String.format(
    206                     "onConfiguredChanged: current layout is %dx%d", width,
    207                     height));
    208         }
    209         /**
    210          * Force wide aspect ratios for landscape
    211          * Force narrow aspect ratios for portrait
    212          */
    213         if (mLastOrientation == Configuration.ORIENTATION_LANDSCAPE) {
    214             if (height > width) {
    215                 int tmp = width;
    216                 width = height;
    217                 height = tmp;
    218             }
    219         } else if (mLastOrientation == Configuration.ORIENTATION_PORTRAIT) {
    220             if (width > height) {
    221                 int tmp = width;
    222                 width = height;
    223                 height = tmp;
    224             }
    225         }
    226 
    227         if (width != params.width && height != params.height) {
    228             if (VERBOSE) {
    229                 Log.v(TAG, String.format(
    230                         "onConfiguredChanged: updating preview size to %dx%d", width,
    231                         height));
    232             }
    233             params.width = width;
    234             params.height = height;
    235 
    236             mPreviewView.setLayoutParams(params);
    237         }
    238     }
    239 
    240     /** SurfaceHolder.Callback methods */
    241     @Override
    242     public void surfaceChanged(SurfaceHolder holder,
    243             int format,
    244             int width,
    245             int height) {
    246         if (VERBOSE) {
    247             Log.v(TAG, String.format("surfaceChanged: format %x, width %d, height %d", format,
    248                     width, height));
    249         }
    250         if (mCurrentPreviewHolder != null && holder == mCurrentPreviewHolder) {
    251             try {
    252                 mCameraOps.minimalPreview(holder);
    253             } catch (ApiFailureException e) {
    254                 logException("Can't start minimal preview: ", e);
    255             }
    256         }
    257     }
    258 
    259     @Override
    260     public void surfaceCreated(SurfaceHolder holder) {
    261 
    262     }
    263 
    264     @Override
    265     public void surfaceDestroyed(SurfaceHolder holder) {
    266     }
    267 
    268     private final Button.OnClickListener mInfoButtonListener = new Button.OnClickListener() {
    269         @Override
    270         public void onClick(View v) {
    271             final Handler uiHandler = new Handler();
    272             AsyncTask.execute(new Runnable() {
    273                 @Override
    274                 public void run() {
    275                     try {
    276                         mCameraOps.minimalJpegCapture(mCaptureListener, mCaptureResultListener,
    277                                 uiHandler, mCameraControl);
    278                         if (mCurrentPreviewHolder != null) {
    279                             mCameraOps.minimalPreview(mCurrentPreviewHolder);
    280                         }
    281                     } catch (ApiFailureException e) {
    282                         logException("Can't take a JPEG! ", e);
    283                     }
    284                 }
    285             });
    286         }
    287     };
    288 
    289     /**
    290      * UI controls enable/disable for all manual controls
    291      */
    292     private void enableManualControls(boolean enabled) {
    293         for (View v : mManualControls) {
    294             v.setEnabled(enabled);
    295         }
    296     }
    297 
    298     private final CameraOps.CaptureListener mCaptureListener = new CameraOps.CaptureListener() {
    299         @Override
    300         public void onCaptureAvailable(Image capture) {
    301             if (capture.getFormat() != ImageFormat.JPEG) {
    302                 Log.e(TAG, "Unexpected format: " + capture.getFormat());
    303                 return;
    304             }
    305             ByteBuffer jpegBuffer = capture.getPlanes()[0].getBuffer();
    306             byte[] jpegData = new byte[jpegBuffer.capacity()];
    307             jpegBuffer.get(jpegData);
    308 
    309             Bitmap b = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
    310             mStillView.setImageBitmap(b);
    311         }
    312     };
    313 
    314     // TODO: this callback is not called for each capture, need figure out why.
    315     private final CameraOps.CaptureResultListener mCaptureResultListener =
    316             new CameraOps.CaptureResultListener() {
    317 
    318                 @Override
    319                 public void onCaptureStarted(CameraDevice camera, CaptureRequest request,
    320                         long timestamp) {
    321                 }
    322 
    323                 @Override
    324                 public void onCaptureCompleted(
    325                         CameraDevice camera, CaptureRequest request, CaptureResult result) {
    326                     Log.i(TAG, "Capture result is available");
    327                     Integer reqCtrlMode;
    328                     Integer resCtrlMode;
    329                     if (request == null || result ==null) {
    330                         Log.e(TAG, "request/result is invalid");
    331                         return;
    332                     }
    333                     Log.i(TAG, "Capture complete");
    334                     final StringBuffer info = new StringBuffer("Capture Result:\n");
    335 
    336                     reqCtrlMode = request.get(CaptureRequest.CONTROL_MODE);
    337                     resCtrlMode = result.get(CaptureResult.CONTROL_MODE);
    338                     info.append("Control mode: request " + reqCtrlMode + ". result " + resCtrlMode);
    339                     info.append("\n");
    340 
    341                     Integer reqSen = request.get(CaptureRequest.SENSOR_SENSITIVITY);
    342                     Integer resSen = result.get(CaptureResult.SENSOR_SENSITIVITY);
    343                     info.append("Sensitivity: request " + reqSen + ". result " + resSen);
    344                     info.append("\n");
    345 
    346                     Long reqExp = request.get(CaptureRequest.SENSOR_EXPOSURE_TIME);
    347                     Long resExp = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
    348                     info.append("Exposure: request " + reqExp + ". result " + resExp);
    349                     info.append("\n");
    350 
    351                     Long reqFD = request.get(CaptureRequest.SENSOR_FRAME_DURATION);
    352                     Long resFD = result.get(CaptureResult.SENSOR_FRAME_DURATION);
    353                     info.append("Frame duration: request " + reqFD + ". result " + resFD);
    354                     info.append("\n");
    355 
    356                     if (mMainHandler != null) {
    357                         mMainHandler.post (new Runnable() {
    358                             @Override
    359                             public void run() {
    360                                 // Update UI for capture result
    361                                 mCaptureResultView.setText(info);
    362                             }
    363                         });
    364                     }
    365                 }
    366 
    367                 @Override
    368                 public void onCaptureFailed(CameraDevice camera, CaptureRequest request,
    369                         CaptureFailure failure) {
    370                     Log.e(TAG, "Capture failed");
    371                 }
    372     };
    373 
    374     private void logException(String msg, Throwable e) {
    375         Log.e(TAG, msg + Log.getStackTraceString(e));
    376     }
    377 
    378     private final OnSeekBarChangeListener mSensitivitySeekBarListener =
    379             new OnSeekBarChangeListener() {
    380 
    381               @Override
    382               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    383                   int[] defaultRange = {MIN_SENSITIVITY, MAX_SENSITIVITY};
    384                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
    385                   int[] sensitivityRange = properties.get(
    386                           CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE);
    387                   if (sensitivityRange == null || sensitivityRange.length < 2 ||
    388                           sensitivityRange[0] > MIN_SENSITIVITY || sensitivityRange[1] < MAX_SENSITIVITY) {
    389                       Log.e(TAG, "unable to get sensitivity range, use default range");
    390                       sensitivityRange = defaultRange;
    391                   }
    392                   int min = sensitivityRange[0];
    393                   int max = sensitivityRange[1];
    394                   float progressFactor = progress / (float)mSeekBarMax;
    395                   int curSensitivity = (int) (min + (max - min) * progressFactor);
    396                   curSensitivity = (curSensitivity / STEP_SIZE ) * STEP_SIZE;
    397                   mCameraControl.setSensitivity(curSensitivity);
    398                   // Update the sensitivity info
    399                   StringBuffer info = new StringBuffer("Sensitivity(ISO):");
    400                   info.append("" + curSensitivity);
    401                   mSensitivityInfoView.setText(info);
    402                   mCameraOps.updatePreview(mCameraControl);
    403               }
    404 
    405               @Override
    406               public void onStartTrackingTouch(SeekBar seekBar) {
    407               }
    408 
    409               @Override
    410               public void onStopTrackingTouch(SeekBar seekBar) {
    411               }
    412     };
    413 
    414     private final OnSeekBarChangeListener mExposureSeekBarListener =
    415             new OnSeekBarChangeListener() {
    416 
    417               @Override
    418               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    419                   long[] defaultRange = {MIN_EXPOSURE, MAX_EXPOSURE};
    420                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
    421                   long[] exposureRange = properties.get(
    422                           CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE);
    423                   // Not enforce the max value check here, most of the devices don't support
    424                   // larger than 30s exposure time
    425                   if (exposureRange == null || exposureRange.length < 2 ||
    426                           exposureRange[0] > MIN_EXPOSURE || exposureRange[1] < 0) {
    427                       exposureRange = defaultRange;
    428                       Log.e(TAG, "exposure time range is invalid, use default range");
    429                   }
    430                   long min = exposureRange[0];
    431                   long max = exposureRange[1];
    432                   float progressFactor = progress / (float)mSeekBarMax;
    433                   long curExposureTime = (long) (min + (max - min) * progressFactor);
    434                   mCameraControl.setExposure(curExposureTime);
    435                   // Update the sensitivity info
    436                   StringBuffer info = new StringBuffer("Exposure Time:");
    437                   info.append("" + curExposureTime / 1000000.0 + "ms");
    438                   mExposureInfoView.setText(info);
    439                   mCameraOps.updatePreview(mCameraControl);
    440               }
    441 
    442               @Override
    443               public void onStartTrackingTouch(SeekBar seekBar) {
    444               }
    445 
    446               @Override
    447               public void onStopTrackingTouch(SeekBar seekBar) {
    448               }
    449     };
    450 
    451     private final OnSeekBarChangeListener mFrameDurationSeekBarListener =
    452             new OnSeekBarChangeListener() {
    453 
    454               @Override
    455               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    456                   CameraCharacteristics properties = mCameraOps.getCameraCharacteristics();
    457                   Long frameDurationMax = properties.get(
    458                           CameraCharacteristics.SENSOR_INFO_MAX_FRAME_DURATION);
    459                   if (frameDurationMax == null || frameDurationMax <= 0) {
    460                       frameDurationMax = MAX_FRAME_DURATION;
    461                       Log.e(TAG, "max frame duration is invalid, set to " + frameDurationMax);
    462                   }
    463                   // Need calculate from different resolution, hard code to 10ms for now.
    464                   long min = 10000000L;
    465                   long max = frameDurationMax;
    466                   float progressFactor = progress / (float)mSeekBarMax;
    467                   long curFrameDuration = (long) (min + (max - min) * progressFactor);
    468                   mCameraControl.setFrameDuration(curFrameDuration);
    469                   // Update the sensitivity info
    470                   StringBuffer info = new StringBuffer("Frame Duration:");
    471                   info.append("" + curFrameDuration / 1000000.0 + "ms");
    472                   mFrameDurationInfoView.setText(info);
    473                   mCameraOps.updatePreview(mCameraControl);
    474               }
    475 
    476               @Override
    477               public void onStartTrackingTouch(SeekBar seekBar) {
    478               }
    479 
    480               @Override
    481               public void onStopTrackingTouch(SeekBar seekBar) {
    482               }
    483     };
    484 
    485     private final View.OnClickListener mControlToggleListener =
    486             new View.OnClickListener() {
    487         @Override
    488         public void onClick(View v) {
    489             boolean enableManual;
    490             if (mManualCtrlToggle.isChecked()) {
    491                 enableManual = true;
    492             } else {
    493                 enableManual = false;
    494             }
    495             mCameraControl.enableManualControl(enableManual);
    496             enableManualControls(enableManual);
    497             mCameraOps.updatePreview(mCameraControl);
    498         }
    499     };
    500 
    501     private final View.OnClickListener mRecordingToggleListener =
    502             new View.OnClickListener() {
    503         @Override
    504         public void onClick(View v) {
    505             if (mRecordingToggle.isChecked()) {
    506                 try {
    507                     mCameraOps.startRecording(/*useMediaCodec*/true);
    508                 } catch (ApiFailureException e) {
    509                     logException("Failed to start recording", e);
    510                 }
    511             } else {
    512                 try {
    513                     mCameraOps.stopRecording();
    514                 } catch (ApiFailureException e) {
    515                     logException("Failed to stop recording", e);
    516                 }
    517             }
    518         }
    519     };
    520 }
    521