• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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.example.android.hdrviewfinder;
18 
19 import android.app.Activity;
20 import android.hardware.camera2.CameraAccessException;
21 import android.hardware.camera2.CameraCaptureSession;
22 import android.hardware.camera2.CameraCharacteristics;
23 import android.hardware.camera2.CameraDevice;
24 import android.hardware.camera2.CameraManager;
25 import android.hardware.camera2.CaptureRequest;
26 import android.hardware.camera2.CaptureResult;
27 import android.hardware.camera2.TotalCaptureResult;
28 import android.hardware.camera2.params.StreamConfigurationMap;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.renderscript.RenderScript;
33 import android.util.Log;
34 import android.util.Size;
35 import android.view.GestureDetector;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.view.MotionEvent;
39 import android.view.Surface;
40 import android.view.SurfaceHolder;
41 import android.view.View;
42 import android.widget.Button;
43 import android.widget.TextView;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 
48 /**
49  * A small demo of advanced camera functionality with the Android camera2 API.
50  *
51  * <p>This demo implements a real-time high-dynamic-range camera viewfinder,
52  * by alternating the sensor's exposure time between two exposure values on even and odd
53  * frames, and then compositing together the latest two frames whenever a new frame is
54  * captured.</p>
55  *
56  * <p>The demo has three modes: Regular auto-exposure viewfinder, split-screen manual exposure,
57  * and the fused HDR viewfinder.  The latter two use manual exposure controlled by the user,
58  * by swiping up/down on the right and left halves of the viewfinder.  The left half controls
59  * the exposure time of even frames, and the right half controls the exposure time of odd frames.
60  * </p>
61  *
62  * <p>In split-screen mode, the even frames are shown on the left and the odd frames on the right,
63  * so the user can see two different exposures of the scene simultaneously.  In fused HDR mode,
64  * the even/odd frames are merged together into a single image.  By selecting different exposure
65  * values for the even/odd frames, the fused image has a higher dynamic range than the regular
66  * viewfinder.</p>
67  *
68  * <p>The HDR fusion and the split-screen viewfinder processing is done with RenderScript; as is the
69  * necessary YUV->RGB conversion. The camera subsystem outputs YUV images naturally, while the GPU
70  * and display subsystems generally only accept RGB data.  Therefore, after the images are
71  * fused/composited, a standard YUV->RGB color transform is applied before the the data is written
72  * to the output Allocation. The HDR fusion algorithm is very simple, and tends to result in
73  * lower-contrast scenes, but has very few artifacts and can run very fast.</p>
74  *
75  * <p>Data is passed between the subsystems (camera, RenderScript, and display) using the
76  * Android {@link android.view.Surface} class, which allows for zero-copy transport of large
77  * buffers between processes and subsystems.</p>
78  */
79 public class HdrViewfinderActivity extends Activity implements
80         SurfaceHolder.Callback, CameraOps.ErrorDisplayer, CameraOps.CameraReadyListener {
81 
82     private static final String TAG = "HdrViewfinderDemo";
83 
84     private static final String FRAGMENT_DIALOG = "dialog";
85 
86     /**
87      * View for the camera preview.
88      */
89     private FixedAspectSurfaceView mPreviewView;
90 
91     /**
92      * This shows the current mode of the app.
93      */
94     private TextView mModeText;
95 
96     // These show lengths of exposure for even frames, exposure for odd frames, and auto exposure.
97     private TextView mEvenExposureText, mOddExposureText, mAutoExposureText;
98 
99     private Handler mUiHandler;
100 
101     private CameraCharacteristics mCameraInfo;
102 
103     private Surface mPreviewSurface;
104     private Surface mProcessingHdrSurface;
105     private Surface mProcessingNormalSurface;
106     CaptureRequest.Builder mHdrBuilder;
107     ArrayList<CaptureRequest> mHdrRequests = new ArrayList<CaptureRequest>(2);
108 
109     CaptureRequest mPreviewRequest;
110 
111     RenderScript mRS;
112     ViewfinderProcessor mProcessor;
113     CameraManager mCameraManager;
114     CameraOps mCameraOps;
115 
116     private int mRenderMode = ViewfinderProcessor.MODE_NORMAL;
117 
118     // Durations in nanoseconds
119     private static final long MICRO_SECOND = 1000;
120     private static final long MILLI_SECOND = MICRO_SECOND * 1000;
121     private static final long ONE_SECOND = MILLI_SECOND * 1000;
122 
123     private long mOddExposure = ONE_SECOND / 33;
124     private long mEvenExposure = ONE_SECOND / 33;
125 
126     private Object mOddExposureTag = new Object();
127     private Object mEvenExposureTag = new Object();
128     private Object mAutoExposureTag = new Object();
129 
130     @Override
onCreate(Bundle savedInstanceState)131     protected void onCreate(Bundle savedInstanceState) {
132         super.onCreate(savedInstanceState);
133         setContentView(R.layout.main);
134 
135         mPreviewView = (FixedAspectSurfaceView) findViewById(R.id.preview);
136         mPreviewView.getHolder().addCallback(this);
137         mPreviewView.setGestureListener(this, mViewListener);
138 
139         Button helpButton = (Button) findViewById(R.id.help_button);
140         helpButton.setOnClickListener(mHelpButtonListener);
141 
142         mModeText = (TextView) findViewById(R.id.mode_label);
143         mEvenExposureText = (TextView) findViewById(R.id.even_exposure);
144         mOddExposureText = (TextView) findViewById(R.id.odd_exposure);
145         mAutoExposureText = (TextView) findViewById(R.id.auto_exposure);
146 
147         mUiHandler = new Handler(Looper.getMainLooper());
148 
149         mCameraManager = (CameraManager) getSystemService(CAMERA_SERVICE);
150         mCameraOps = new CameraOps(mCameraManager,
151                 /*errorDisplayer*/ this,
152                 /*readyListener*/ this,
153                 /*readyHandler*/ mUiHandler);
154 
155         mHdrRequests.add(null);
156         mHdrRequests.add(null);
157 
158         mRS = RenderScript.create(this);
159     }
160 
161     @Override
onResume()162     protected void onResume() {
163         super.onResume();
164 
165         findAndOpenCamera();
166     }
167 
168     @Override
onPause()169     protected void onPause() {
170         super.onPause();
171 
172         // Wait until camera is closed to ensure the next application can open it
173         mCameraOps.closeCameraAndWait();
174     }
175 
176     @Override
onCreateOptionsMenu(Menu menu)177     public boolean onCreateOptionsMenu(Menu menu) {
178         getMenuInflater().inflate(R.menu.main, menu);
179         return super.onCreateOptionsMenu(menu);
180     }
181 
182     @Override
onOptionsItemSelected(MenuItem item)183     public boolean onOptionsItemSelected(MenuItem item) {
184         switch (item.getItemId()) {
185             case R.id.info: {
186                 MessageDialogFragment.newInstance(R.string.intro_message)
187                         .show(getFragmentManager(), FRAGMENT_DIALOG);
188                 break;
189             }
190         }
191         return super.onOptionsItemSelected(item);
192     }
193 
194     private GestureDetector.OnGestureListener mViewListener
195             = new GestureDetector.SimpleOnGestureListener() {
196 
197         @Override
198         public boolean onDown(MotionEvent e) {
199             return true;
200         }
201 
202         @Override
203         public boolean onSingleTapUp(MotionEvent e) {
204             switchRenderMode(1);
205             return true;
206         }
207 
208         @Override
209         public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
210             if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) return false;
211 
212             float xPosition = e1.getAxisValue(MotionEvent.AXIS_X);
213             float width = mPreviewView.getWidth();
214             float height = mPreviewView.getHeight();
215 
216             float xPosNorm = xPosition / width;
217             float yDistNorm = distanceY / height;
218 
219             final float ACCELERATION_FACTOR = 8;
220             double scaleFactor = Math.pow(2.f, yDistNorm * ACCELERATION_FACTOR);
221 
222             // Even on left, odd on right
223             if (xPosNorm > 0.5) {
224                 mOddExposure *= scaleFactor;
225             } else {
226                 mEvenExposure *= scaleFactor;
227             }
228 
229             setHdrBurst();
230 
231             return true;
232         }
233     };
234 
235     // Show help dialog
236     private View.OnClickListener mHelpButtonListener = new View.OnClickListener() {
237         public void onClick(View v) {
238             MessageDialogFragment.newInstance(R.string.help_text)
239                     .show(getFragmentManager(), FRAGMENT_DIALOG);
240         }
241     };
242 
findAndOpenCamera()243     private void findAndOpenCamera() {
244 
245         String errorMessage = "Unknown error";
246         boolean foundCamera = false;
247         try {
248             // Find first back-facing camera that has necessary capability
249             String[] cameraIds = mCameraManager.getCameraIdList();
250             for (String id : cameraIds) {
251                 CameraCharacteristics info = mCameraManager.getCameraCharacteristics(id);
252                 int facing = info.get(CameraCharacteristics.LENS_FACING);
253 
254                 int level = info.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
255                 boolean hasFullLevel
256                         = (level == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
257 
258                 int[] capabilities = info.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
259                 int syncLatency = info.get(CameraCharacteristics.SYNC_MAX_LATENCY);
260                 boolean hasManualControl = hasCapability(capabilities,
261                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR);
262                 boolean hasEnoughCapability = hasManualControl &&
263                         syncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL;
264 
265                 // All these are guaranteed by
266                 // CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, but checking for only
267                 // the things we care about expands range of devices we can run on
268                 // We want:
269                 //  - Back-facing camera
270                 //  - Manual sensor control
271                 //  - Per-frame synchronization (so that exposure can be changed every frame)
272                 if (facing == CameraCharacteristics.LENS_FACING_BACK &&
273                         (hasFullLevel || hasEnoughCapability)) {
274                     // Found suitable camera - get info, open, and set up outputs
275                     mCameraInfo = info;
276                     mCameraOps.openCamera(id);
277                     configureSurfaces();
278                     foundCamera = true;
279                     break;
280                 }
281             }
282             if (!foundCamera) {
283                 errorMessage = getString(R.string.camera_no_good);
284             }
285         } catch (CameraAccessException e) {
286             errorMessage = getErrorString(e);
287         }
288 
289         if (!foundCamera) {
290             showErrorDialog(errorMessage);
291         }
292     }
293 
hasCapability(int[] capabilities, int capability)294     private boolean hasCapability(int[] capabilities, int capability) {
295         for (int c : capabilities) {
296             if (c == capability) return true;
297         }
298         return false;
299     }
300 
switchRenderMode(int direction)301     private void switchRenderMode(int direction) {
302         mRenderMode = (mRenderMode + direction) % 3;
303 
304         mModeText.setText(getResources().getStringArray(R.array.mode_label_array)[mRenderMode]);
305 
306         if (mProcessor != null) {
307             mProcessor.setRenderMode(mRenderMode);
308         }
309         if (mRenderMode == ViewfinderProcessor.MODE_NORMAL) {
310             mCameraOps.setRepeatingRequest(mPreviewRequest,
311                     mCaptureCallback, mUiHandler);
312         } else {
313             setHdrBurst();
314         }
315     }
316 
317     /**
318      * Configure the surfaceview and RS processing
319      */
configureSurfaces()320     private void configureSurfaces() {
321         // Find a good size for output - largest 16:9 aspect ratio that's less than 720p
322         final int MAX_WIDTH = 1280;
323         final float TARGET_ASPECT = 16.f / 9.f;
324         final float ASPECT_TOLERANCE = 0.1f;
325 
326         StreamConfigurationMap configs =
327                 mCameraInfo.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
328 
329         Size[] outputSizes = configs.getOutputSizes(SurfaceHolder.class);
330 
331         Size outputSize = outputSizes[0];
332         float outputAspect = (float) outputSize.getWidth() / outputSize.getHeight();
333         for (Size candidateSize : outputSizes) {
334             if (candidateSize.getWidth() > MAX_WIDTH) continue;
335             float candidateAspect = (float) candidateSize.getWidth() / candidateSize.getHeight();
336             boolean goodCandidateAspect =
337                     Math.abs(candidateAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
338             boolean goodOutputAspect =
339                     Math.abs(outputAspect - TARGET_ASPECT) < ASPECT_TOLERANCE;
340             if ((goodCandidateAspect && !goodOutputAspect) ||
341                     candidateSize.getWidth() > outputSize.getWidth()) {
342                 outputSize = candidateSize;
343                 outputAspect = candidateAspect;
344             }
345         }
346         Log.i(TAG, "Resolution chosen: " + outputSize);
347 
348         // Configure processing
349         mProcessor = new ViewfinderProcessor(mRS, outputSize);
350         setupProcessor();
351 
352         // Configure the output view - this will fire surfaceChanged
353         mPreviewView.setAspectRatio(outputAspect);
354         mPreviewView.getHolder().setFixedSize(outputSize.getWidth(), outputSize.getHeight());
355     }
356 
357     /**
358      * Once camera is open and output surfaces are ready, configure the RS processing
359      * and the camera device inputs/outputs.
360      */
361     private void setupProcessor() {
362         if (mProcessor == null || mPreviewSurface == null) return;
363 
364         mProcessor.setOutputSurface(mPreviewSurface);
365         mProcessingHdrSurface = mProcessor.getInputHdrSurface();
366         mProcessingNormalSurface = mProcessor.getInputNormalSurface();
367 
368         List<Surface> cameraOutputSurfaces = new ArrayList<Surface>();
369         cameraOutputSurfaces.add(mProcessingHdrSurface);
370         cameraOutputSurfaces.add(mProcessingNormalSurface);
371 
372         mCameraOps.setSurfaces(cameraOutputSurfaces);
373     }
374 
375     /**
376      * Start running an HDR burst on a configured camera session
377      */
378     public void setHdrBurst() {
379 
380         mHdrBuilder.set(CaptureRequest.SENSOR_SENSITIVITY, 1600);
381         mHdrBuilder.set(CaptureRequest.SENSOR_FRAME_DURATION, ONE_SECOND / 30);
382 
383         mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, mEvenExposure);
384         mHdrBuilder.setTag(mEvenExposureTag);
385         mHdrRequests.set(0, mHdrBuilder.build());
386 
387         mHdrBuilder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, mOddExposure);
388         mHdrBuilder.setTag(mOddExposureTag);
389         mHdrRequests.set(1, mHdrBuilder.build());
390 
391         mCameraOps.setRepeatingBurst(mHdrRequests, mCaptureCallback, mUiHandler);
392     }
393 
394     /**
395      * Listener for completed captures
396      * Invoked on UI thread
397      */
398     private CameraCaptureSession.CaptureCallback mCaptureCallback
399             = new CameraCaptureSession.CaptureCallback() {
400 
401         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
402                                        TotalCaptureResult result) {
403 
404             // Only update UI every so many frames
405             // Use an odd number here to ensure both even and odd exposures get an occasional update
406             long frameNumber = result.getFrameNumber();
407             if (frameNumber % 3 != 0) return;
408 
409             long exposureTime = result.get(CaptureResult.SENSOR_EXPOSURE_TIME);
410 
411             // Format exposure time nicely
412             String exposureText;
413             if (exposureTime > ONE_SECOND) {
414                 exposureText = String.format("%.2f s", exposureTime / 1e9);
415             } else if (exposureTime > MILLI_SECOND) {
416                 exposureText = String.format("%.2f ms", exposureTime / 1e6);
417             } else if (exposureTime > MICRO_SECOND) {
418                 exposureText = String.format("%.2f us", exposureTime / 1e3);
419             } else {
420                 exposureText = String.format("%d ns", exposureTime);
421             }
422 
423             Object tag = request.getTag();
424             Log.i(TAG, "Exposure: " + exposureText);
425 
426             if (tag == mEvenExposureTag) {
427                 mEvenExposureText.setText(exposureText);
428 
429                 mEvenExposureText.setEnabled(true);
430                 mOddExposureText.setEnabled(true);
431                 mAutoExposureText.setEnabled(false);
432             } else if (tag == mOddExposureTag) {
433                 mOddExposureText.setText(exposureText);
434 
435                 mEvenExposureText.setEnabled(true);
436                 mOddExposureText.setEnabled(true);
437                 mAutoExposureText.setEnabled(false);
438             } else {
439                 mAutoExposureText.setText(exposureText);
440 
441                 mEvenExposureText.setEnabled(false);
442                 mOddExposureText.setEnabled(false);
443                 mAutoExposureText.setEnabled(true);
444             }
445         }
446     };
447 
448     /**
449      * Callbacks for the FixedAspectSurfaceView
450      */
451 
452     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)453     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
454         mPreviewSurface = holder.getSurface();
455 
456         setupProcessor();
457     }
458 
459     @Override
surfaceCreated(SurfaceHolder holder)460     public void surfaceCreated(SurfaceHolder holder) {
461         // ignored
462     }
463 
464     @Override
surfaceDestroyed(SurfaceHolder holder)465     public void surfaceDestroyed(SurfaceHolder holder) {
466         mPreviewSurface = null;
467     }
468 
469     /**
470      * Callbacks for CameraOps
471      */
472     @Override
onCameraReady()473     public void onCameraReady() {
474         // Ready to send requests in, so set them up
475         try {
476             CaptureRequest.Builder previewBuilder =
477                     mCameraOps.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
478             previewBuilder.addTarget(mProcessingNormalSurface);
479             previewBuilder.setTag(mAutoExposureTag);
480             mPreviewRequest = previewBuilder.build();
481 
482             mHdrBuilder =
483                     mCameraOps.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
484             mHdrBuilder.set(CaptureRequest.CONTROL_AE_MODE,
485                     CaptureRequest.CONTROL_AE_MODE_OFF);
486             mHdrBuilder.addTarget(mProcessingHdrSurface);
487 
488             switchRenderMode(0);
489 
490         } catch (CameraAccessException e) {
491             String errorMessage = getErrorString(e);
492             showErrorDialog(errorMessage);
493         }
494     }
495 
496     /**
497      * Utility methods
498      */
499     @Override
showErrorDialog(String errorMessage)500     public void showErrorDialog(String errorMessage) {
501         MessageDialogFragment.newInstance(errorMessage).show(getFragmentManager(), FRAGMENT_DIALOG);
502     }
503 
504     @Override
getErrorString(CameraAccessException e)505     public String getErrorString(CameraAccessException e) {
506         String errorMessage;
507         switch (e.getReason()) {
508             case CameraAccessException.CAMERA_DISABLED:
509                 errorMessage = getString(R.string.camera_disabled);
510                 break;
511             case CameraAccessException.CAMERA_DISCONNECTED:
512                 errorMessage = getString(R.string.camera_disconnected);
513                 break;
514             case CameraAccessException.CAMERA_ERROR:
515                 errorMessage = getString(R.string.camera_error);
516                 break;
517             default:
518                 errorMessage = getString(R.string.camera_unknown, e.getReason());
519                 break;
520         }
521         return errorMessage;
522     }
523 
524 }
525