-
-
Save gohilbhagirath90/bd5e0551e6ab9a0f3569a22a0b8184da to your computer and use it in GitHub Desktop.
Quick-n-Dirty Camera2 Activity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.natevogtphoto.camera2test; | |
import android.app.Activity; | |
import android.content.Context; | |
import android.content.pm.ActivityInfo; | |
import android.graphics.ImageFormat; | |
import android.graphics.SurfaceTexture; | |
import android.hardware.camera2.CameraAccessException; | |
import android.hardware.camera2.CameraCaptureSession; | |
import android.hardware.camera2.CameraCaptureSession.CaptureCallback; | |
import android.hardware.camera2.CameraCaptureSession.StateCallback; | |
import android.hardware.camera2.CameraCharacteristics; | |
import android.hardware.camera2.CameraDevice; | |
import android.hardware.camera2.CameraManager; | |
import android.hardware.camera2.CameraMetadata; | |
import android.hardware.camera2.CaptureFailure; | |
import android.hardware.camera2.CaptureRequest; | |
import android.hardware.camera2.CaptureResult; | |
import android.hardware.camera2.DngCreator; | |
import android.hardware.camera2.TotalCaptureResult; | |
import android.hardware.camera2.params.StreamConfigurationMap; | |
import android.media.Image; | |
import android.media.ImageReader; | |
import android.media.MediaScannerConnection; | |
import android.os.AsyncTask; | |
import android.os.Bundle; | |
import android.os.Environment; | |
import android.support.annotation.NonNull; | |
import android.util.Log; | |
import android.util.Size; | |
import android.view.Surface; | |
import android.view.TextureView; | |
import android.view.View; | |
import android.view.ViewGroup.LayoutParams; | |
import android.view.WindowManager; | |
import android.widget.Toast; | |
import java.io.File; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.lang.ref.WeakReference; | |
import java.nio.ByteBuffer; | |
import java.util.Arrays; | |
import java.util.List; | |
public class Camera2Activity extends Activity implements TextureView.SurfaceTextureListener { | |
private static final String TAG = "Camera2Test"; | |
private CameraDevice mCamera; | |
private CameraCaptureSession mSession; | |
private TextureView mPreviewView; | |
private Surface mRawCaptureSurface, mJpegCaptureSurface, mPreviewSurface; | |
private CaptureResult mPendingResult; | |
private Size mPreviewSize; | |
private File mPhotoDir; | |
private CameraCharacteristics mCharacteristics; | |
private int mCaptureImageFormat; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); | |
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | | |
View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); | |
File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); | |
mPhotoDir = new File(picturesDir, getString(R.string.app_name)); | |
if (!mPhotoDir.exists()) { | |
mPhotoDir.mkdir(); | |
} | |
// presumably your layout will have more than just a texture view, for example a capture button.. | |
mPreviewView = new TextureView(this); | |
setContentView(mPreviewView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); | |
// wait for surface to be created | |
mPreviewView.setSurfaceTextureListener(this); | |
} | |
@Override | |
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { | |
try { | |
initCamera(surface); | |
} catch (CameraAccessException e) { | |
Log.e(TAG, "Failed to open camera", e); | |
} | |
} | |
@Override | |
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { | |
if (mCamera != null) { | |
mCamera.close(); | |
mCamera = null; | |
} | |
mSession = null; | |
return true; | |
} | |
@Override | |
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { | |
} | |
@Override | |
public void onSurfaceTextureUpdated(SurfaceTexture surface) { | |
} | |
private void initCamera(SurfaceTexture surface) throws CameraAccessException { | |
CameraManager cm = (CameraManager) getSystemService(CAMERA_SERVICE); | |
// get ID of rear-facing camera | |
String[] cameraIds = cm.getCameraIdList(); | |
String cameraId = null; | |
CameraCharacteristics cc = null; | |
for (String id : cameraIds) { | |
cc = cm.getCameraCharacteristics(id); | |
if (cc.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_BACK) { | |
cameraId = id; | |
break; | |
} | |
} | |
if (cameraId == null) { | |
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, "Couldn't find suitable camera"); | |
} | |
mCharacteristics = cc; | |
StreamConfigurationMap streamConfigs = cc.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); | |
// determine supported output formats.. | |
boolean supportsRaw = false, supportsJpeg = false; | |
for (int format : streamConfigs.getOutputFormats()) { | |
if (format == ImageFormat.RAW_SENSOR) { | |
supportsRaw = true; | |
} else if (format == ImageFormat.JPEG) { | |
supportsJpeg = true; | |
} | |
} | |
if (supportsRaw) { | |
mCaptureImageFormat = ImageFormat.RAW_SENSOR; | |
} else if (supportsJpeg) { | |
mCaptureImageFormat = ImageFormat.JPEG; | |
} else { | |
throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, "Couldn't find supported image format"); | |
} | |
// alternatively, make a way for the user to select a capture size.. | |
Size rawSize = streamConfigs.getOutputSizes(ImageFormat.RAW_SENSOR)[0]; | |
Size jpegSize = streamConfigs.getOutputSizes(ImageFormat.JPEG)[0]; | |
// find the preview size that best matches the aspect ratio of the camera sensor.. | |
Size[] previewSizes = streamConfigs.getOutputSizes(SurfaceTexture.class); | |
mPreviewSize = findOptimalPreviewSize(previewSizes, rawSize); | |
if (mPreviewSize == null) { | |
return; | |
} | |
// set up capture surfaces and image readers.. | |
mPreviewSurface = new Surface(surface); | |
ImageReader rawReader = ImageReader.newInstance(rawSize.getWidth(), rawSize.getHeight(), | |
ImageFormat.RAW_SENSOR, 1); | |
rawReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { | |
@Override | |
public void onImageAvailable(ImageReader reader) { | |
new SaveRawTask(Camera2Activity.this, mPhotoDir, reader.acquireLatestImage(), | |
mCharacteristics, mPendingResult).execute(); | |
} | |
}, null); | |
mRawCaptureSurface = rawReader.getSurface(); | |
ImageReader jpegReader = ImageReader.newInstance(jpegSize.getWidth(), jpegSize.getHeight(), | |
ImageFormat.JPEG, 1); | |
jpegReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { | |
@Override | |
public void onImageAvailable(ImageReader reader) { | |
new SaveJpegTask(Camera2Activity.this, mPhotoDir, reader.acquireLatestImage()).execute(); | |
} | |
}, null); | |
mJpegCaptureSurface = jpegReader.getSurface(); | |
cm.openCamera(cameraId, new CameraDevice.StateCallback() { | |
@Override | |
public void onOpened(@NonNull CameraDevice camera) { | |
mCamera = camera; | |
initPreview(); | |
} | |
@Override | |
public void onDisconnected(@NonNull CameraDevice camera) { | |
} | |
@Override | |
public void onError(@NonNull CameraDevice camera, int error) { | |
} | |
}, null); | |
} | |
private void initPreview() { | |
// scale preview size to fill screen width | |
int screenWidth = getResources().getDisplayMetrics().widthPixels; | |
float previewRatio = mPreviewSize.getWidth() / ((float) mPreviewSize.getHeight()); | |
int previewHeight = Math.round(screenWidth * previewRatio); | |
LayoutParams params = mPreviewView.getLayoutParams(); | |
params.width = screenWidth; | |
params.height = previewHeight; | |
List<Surface> surfaces = Arrays.asList(mPreviewSurface, mRawCaptureSurface, mJpegCaptureSurface); | |
try { | |
mCamera.createCaptureSession(surfaces, new StateCallback() { | |
@Override | |
public void onConfigured(CameraCaptureSession session) { | |
mSession = session; | |
updatePreview(); | |
} | |
@Override | |
public void onConfigureFailed(CameraCaptureSession session) { | |
} | |
}, null); | |
} catch (CameraAccessException e) { | |
Log.d(TAG, "Failed to create camera capture session", e); | |
} | |
} | |
/** | |
* Call this whenever some camera control changes (e.g., focus distance, white balance, etc) that should affect the preview | |
*/ | |
private void updatePreview() { | |
try { | |
if (mCamera == null || mSession == null) { | |
return; | |
} | |
CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); | |
builder.addTarget(mPreviewSurface); | |
builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF); | |
// builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, ...) | |
// builder.set(CaptureRequest.SENSOR_SENSITIVITY, ...) | |
// builder.set(CaptureRequest.CONTROL_AWB_MODE, ...) | |
// builder.set(CaptureRequest.CONTROL_EFFECT_MODE, ...) | |
// builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, ...) | |
// etc... | |
mSession.setRepeatingRequest(builder.build(), new CaptureCallback() { | |
@Override | |
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { | |
// if desired, we can get updated auto focus & auto exposure values here from 'result' | |
} | |
}, null); | |
} catch (CameraAccessException e) { | |
Log.e(TAG, "Failed to start preview"); | |
} | |
} | |
/** | |
* This should be triggered by a capture button press or something similar | |
*/ | |
public void capture() { | |
try { | |
CaptureRequest.Builder builder = mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); | |
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); | |
// we probably don't want to be auto focusing while an image is being captured. | |
builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_OFF); | |
builder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE); | |
// set options here that the user has changed | |
// builder.set(CaptureRequest.LENS_FOCUS_DISTANCE, ...) | |
// builder.set(CaptureRequest.CONTROL_AWB_MODE, ...) | |
// builder.set(CaptureRequest.CONTROL_EFFECT_MODE, ...) | |
// builder.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, ...) | |
// etc... | |
builder.set(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE, CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON); | |
if (mCaptureImageFormat == ImageFormat.JPEG) { | |
builder.addTarget(mJpegCaptureSurface); | |
builder.set(CaptureRequest.JPEG_QUALITY, (byte) 100); | |
} else { | |
builder.addTarget(mRawCaptureSurface); | |
builder.set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE, CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE_ON); | |
} | |
mSession.capture(builder.build(), new CaptureCallback() { | |
@Override | |
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { | |
// save this, as it's needed to create raw files | |
mPendingResult = result; | |
} | |
@Override | |
public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) { | |
super.onCaptureFailed(session, request, failure); | |
Log.e(TAG, "Image capture failed"); | |
} | |
}, null); | |
} catch (CameraAccessException e) { | |
Log.e(TAG, "Image capture failed", e); | |
} | |
} | |
private static class SaveRawTask extends AsyncTask<Void, Void, Boolean> { | |
private WeakReference<Context> mContextRef; | |
private File mFile; | |
private Image mImage; | |
private DngCreator mDngCreator; | |
public SaveRawTask(Context context, File dir, Image image, CameraCharacteristics characteristics, CaptureResult metadata) { | |
mContextRef = new WeakReference<>(context); | |
mFile = new File(dir, System.currentTimeMillis() + ".dng"); | |
mImage = image; | |
mDngCreator = new DngCreator(characteristics, metadata); | |
} | |
@Override | |
protected Boolean doInBackground(Void... params) { | |
try { | |
mDngCreator.writeImage(new FileOutputStream(mFile), mImage); | |
mDngCreator.close(); | |
mImage.close(); | |
return true; | |
} catch (IOException e) { | |
return false; | |
} | |
} | |
@Override | |
protected void onPostExecute(Boolean result) { | |
Context context = mContextRef.get(); | |
if (context != null) { | |
if (result) { | |
MediaScannerConnection.scanFile(context, new String[]{mFile.getAbsolutePath()}, null, null); | |
Toast.makeText(context, "Image captured!", Toast.LENGTH_SHORT).show(); | |
} else { | |
Toast.makeText(context, "Error saving image", Toast.LENGTH_LONG).show(); | |
} | |
} | |
} | |
} | |
private static class SaveJpegTask extends AsyncTask<Void, Void, Boolean> { | |
private File mFile; | |
private Image mImage; | |
private WeakReference<Context> mContextRef; | |
public SaveJpegTask(Context context, File dir, Image image) { | |
mContextRef = new WeakReference<>(context); | |
mFile = new File(dir, System.currentTimeMillis() + ".jpg"); | |
mImage = image; | |
} | |
@Override | |
protected Boolean doInBackground(Void... params) { | |
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer(); | |
byte[] bytes = new byte[buffer.capacity()]; | |
buffer.get(bytes); | |
mImage.close(); | |
try { | |
new FileOutputStream(mFile).write(bytes); | |
return true; | |
} catch (IOException e) { | |
return false; | |
} | |
} | |
@Override | |
protected void onPostExecute(Boolean result) { | |
Context context = mContextRef.get(); | |
if (context != null) { | |
if (result) { | |
MediaScannerConnection.scanFile(context, new String[]{mFile.getAbsolutePath()}, null, null); | |
Toast.makeText(context, "Image captured!", Toast.LENGTH_SHORT).show(); | |
} else { | |
Toast.makeText(context, "Error saving image", Toast.LENGTH_LONG).show(); | |
} | |
} | |
} | |
} | |
/** | |
* Given a target size for raw output, search available preview sizes for one with a similar | |
* aspect ratio that does not exceed screen size. | |
*/ | |
private Size findOptimalPreviewSize(Size[] sizes, Size targetSize) { | |
float targetRatio = targetSize.getWidth() * 1.0f / targetSize.getHeight(); | |
float tolerance = 0.1f; | |
int screenWidth = getResources().getDisplayMetrics().widthPixels; | |
int maxPixels = screenWidth * Math.round(screenWidth * targetRatio); | |
int width, height; | |
float ratio; | |
for (Size size : sizes) { | |
width = size.getWidth(); | |
height = size.getHeight(); | |
if (width * height <= maxPixels) { | |
ratio = ((float) width) / height; | |
if (Math.abs(ratio - targetRatio) < tolerance) { | |
return size; | |
} | |
} | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment