package com.example.myapplication;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class Camera2Helper {
private static final String TAG = "Camera2Helper";
private String mCameraId;
private String specificCameraId;
private TextureView mTextureView;
private int rotation;
private boolean isMirror;
private Point previewViewSize;
private Point specificPreviewSize;
private Context context;
/**
* A {@link CameraCaptureSession } for camera preview.
*/
private CameraCaptureSession mCaptureSession;
private CameraDevice mCameraDevice;
public static final String CAMERA_ID_FRONT = "1";
public static final String CAMERA_ID_BACK = "0";
/**
* Orientation of the camera sensor
*/
private int mSensorOrientation;
private Size mPreviewSize;
private Point maxPreviewSize;
private Point minPreviewSize;
private Camera2Helper(Camera2Helper.Builder builder){
context = builder.context;
specificCameraId = builder.specificCameraId;
isMirror = builder.isMirror;
rotation = builder.rotation;
previewViewSize = builder.previewViewSize;
specificPreviewSize = builder.previewSize;
maxPreviewSize = builder.maxPreviewSize;
minPreviewSize = builder.minPreviewSize;
mTextureView = builder.previewDisplayView;
}
private int getCameraOri(int rotation, String cameraId) {
int degrees = rotation * 90;
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;
default:
break;
}
int result;
if (CAMERA_ID_FRONT.equals(cameraId)) {
result = (mSensorOrientation + degrees) % 360;
result = (360 - result) % 360;
} else {
result = (mSensorOrientation - degrees + 360) % 360;
}
Log.i(TAG, "getCameraOri: " + rotation + " " + result + " " + mSensorOrientation);
return result;
}
/**
* An additional thread for running tasks that shouldn't block the UI.
*/
private HandlerThread mBackgroundThread;
/**
* A {@link Handler} for running tasks in the background.
*/
private Handler mBackgroundHandler;
private ImageReader mImageReader;
/**
* {@link CaptureRequest.Builder} for the camera preview
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* Starts a background thread and its {@link Handler}.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
private Size getBestSupportedSize(List<Size> sizes) {
Size defaultSize = sizes.get(0);
Size[] tempSizes = sizes.toArray(new Size[0]);
Arrays.sort(tempSizes, new Comparator<Size>() {
@Override
public int compare(Size o1, Size o2) {
if (o1.getWidth() > o2.getWidth()) {
return -1;
} else if (o1.getWidth() == o2.getWidth()) {
return o1.getHeight() > o2.getHeight() ? -1 : 1;
} else {
return 1;
}
}
});
sizes = new ArrayList<>(Arrays.asList(tempSizes));
for (int i = sizes.size() - 1; i >= 0; i--) {
if (maxPreviewSize != null) {
if (sizes.get(i).getWidth() > maxPreviewSize.x || sizes.get(i).getHeight() > maxPreviewSize.y) {
sizes.remove(i);
continue;
}
}
if (minPreviewSize != null) {
if (sizes.get(i).getWidth() < minPreviewSize.x || sizes.get(i).getHeight() < minPreviewSize.y) {
sizes.remove(i);
}
}
}
if (sizes.size() == 0) {
String msg = "can not find suitable previewSize, now using default";
// if (camera2Listener != null) {
// Log.e(TAG, msg);
// camera2Listener.onCameraError(new Exception(msg));
// }
return defaultSize;
}
Size bestSize = sizes.get(0);
float previewViewRatio;
if (previewViewSize != null) {
previewViewRatio = (float) previewViewSize.x / (float) previewViewSize.y;
} else {
previewViewRatio = (float) bestSize.getWidth() / (float) bestSize.getHeight();
}
if (previewViewRatio > 1) {
previewViewRatio = 1 / previewViewRatio;
}
for (Size s : sizes) {
if (specificPreviewSize != null && specificPreviewSize.x == s.getWidth() && specificPreviewSize.y == s.getHeight()) {
return s;
}
if (Math.abs((s.getHeight() / (float) s.getWidth()) - previewViewRatio) < Math.abs(bestSize.getHeight() / (float) bestSize.getWidth() - previewViewRatio)) {
bestSize = s;
}
}
return bestSize;
}
private boolean configCameraParams(CameraManager manager, String cameraId) throws CameraAccessException {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
return false;
}
mPreviewSize = getBestSupportedSize(new ArrayList<Size>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.YUV_420_888, 2);
// mImageReader.setOnImageAvailableListener(
// new OnImageAvailableListenerImpl(), mBackgroundHandler);
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
mCameraId = cameraId;
return true;
}
private void setUpCameraOutputs(CameraManager cameraManager) {
try {
if (configCameraParams(cameraManager, specificCameraId)) {
return;
}
for (String cameraId : cameraManager.getCameraIdList()) {
if (configCameraParams(cameraManager, cameraId)) {
return;
}
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
// if (camera2Listener != null) {
// camera2Listener.onCameraError(e);
// }
}
}
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mTextureView || null == mPreviewSize) {
return;
}
Matrix matrix = new Matrix();
RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth());
float centerX = viewRect.centerX();
float centerY = viewRect.centerY();
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max(
(float) viewHeight / mPreviewSize.getHeight(),
(float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate((90 * (rotation - 2)) % 360, centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
Log.i(TAG, "configureTransform: " + getCameraOri(rotation, mCameraId) + " " + rotation * 90);
mTextureView.setTransform(matrix);
}
private void openCamera() {
CameraManager cameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
setUpCameraOutputs(cameraManager);
configureTransform(mTextureView.getWidth(), mTextureView.getHeight());
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
cameraManager.openCamera(mCameraId, mDeviceStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
// if (camera2Listener != null) {
// camera2Listener.onCameraError(e);
// }
}catch (InterruptedException e){
Log.e(TAG,"InterruptedException -------------");
} catch (SecurityException e){
// if (camera2Listener != null) {
// camera2Listener.onCameraError(e);
// }
}
}
/**
* A {@link Semaphore} to prevent the app from exiting before closing the camera.
*/
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
/**
* Closes the current {@link CameraDevice}.
*/
private void closeCamera() {
try {
mCameraOpenCloseLock.acquire();
if (null != mCaptureSession) {
mCaptureSession.close();
mCaptureSession = null;
}
if (null != mCameraDevice) {
mCameraDevice.close();
mCameraDevice = null;
}
if (null != mImageReader) {
mImageReader.close();
mImageReader = null;
}
// if (camera2Listener != null) {
// camera2Listener.onCameraClosed();
// }
} catch (InterruptedException e) {
// if (camera2Listener != null) {
// camera2Listener.onCameraError(e);
// }
} finally {
mCameraOpenCloseLock.release();
}
}
public synchronized void start() {
if (mCameraDevice != null) {
return;
}
startBackgroundThread();
if (mTextureView.isAvailable()) {
openCamera();
} else {
mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
}
}
public synchronized void stop() {
if (mCameraDevice == null) {
return;
}
closeCamera();
stopBackgroundThread();
}
/**
* Stops the background thread and its {@link Handler}.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
Log.i(TAG, "onSurfaceTextureAvailable: ");
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
Log.i(TAG, "onSurfaceTextureSizeChanged: ");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
Log.i(TAG, "onSurfaceTextureDestroyed: ");
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
private CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
Log.i(TAG, "onConfigured: ");
// The camera is already closed
if (null == mCameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
mCaptureSession = cameraCaptureSession;
try {
mCaptureSession.setRepeatingRequest(mPreviewRequestBuilder.build(),
new CameraCaptureSession.CaptureCallback() {
}, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.i(TAG, "onConfigureFailed: ");
// if (camera2Listener != null) {
// camera2Listener.onCameraError(new Exception("configureFailed"));
// }
}
};
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = mTextureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
mPreviewRequestBuilder.addTarget(surface);
mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
// Here, we create a CameraCaptureSession for camera preview.
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
mCaptureStateCallback, mBackgroundHandler
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private CameraDevice.StateCallback mDeviceStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
Log.i(TAG, "onOpened: ");
// This method is called when the camera is opened. We start camera preview here.
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
// if (camera2Listener != null) {
// camera2Listener.onCameraOpened(cameraDevice, mCameraId, mPreviewSize, getCameraOri(rotation, mCameraId), isMirror);
// }
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
Log.i(TAG, "onDisconnected: ");
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
// if (camera2Listener != null) {
// camera2Listener.onCameraClosed();
// }
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
Log.i(TAG, "onError: ");
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
// if (camera2Listener != null) {
// camera2Listener.onCameraError(new Exception("error occurred, code is " + error));
// }
}
};
public static final class Builder {
/**
* 预览显示的view,目前仅支持textureView
*/
private TextureView previewDisplayView;
/**
* 是否镜像显示,只支持textureView
*/
private boolean isMirror;
/**
* 指定的相机ID
*/
private String specificCameraId;
/**
* 事件回调
*/
//private Camera2Listener camera2Listener;
/**
* 屏幕的长宽,在选择最佳相机比例时用到
*/
private Point previewViewSize;
/**
* 传入getWindowManager().getDefaultDisplay().getRotation()的值即可
*/
private int rotation;
/**
* 指定的预览宽高,若系统支持则会以这个预览宽高进行预览
*/
private Point previewSize;
/**
* 最大分辨率
*/
private Point maxPreviewSize;
/**
* 最小分辨率
*/
private Point minPreviewSize;
/**
* 上下文,用于获取CameraManager
*/
private Context context;
public Builder() {
}
public Builder previewOn(TextureView val) {
previewDisplayView = val;
return this;
}
public Builder isMirror(boolean val) {
isMirror = val;
return this;
}
public Builder previewSize(Point val) {
previewSize = val;
return this;
}
public Builder maxPreviewSize(Point val) {
maxPreviewSize = val;
return this;
}
public Builder minPreviewSize(Point val) {
minPreviewSize = val;
return this;
}
public Builder previewViewSize(Point val) {
previewViewSize = val;
return this;
}
public Builder rotation(int val) {
rotation = val;
return this;
}
public Builder specificCameraId(String val) {
specificCameraId = val;
return this;
}
// public Builder cameraListener(Camera2Listener val) {
// camera2Listener = val;
// return this;
// }
public Builder context(Context val) {
context = val;
return this;
}
public Camera2Helper build() {
if (previewViewSize == null) {
Log.e(TAG, "previewViewSize is null, now use default previewSize");
}
// if (camera2Listener == null) {
// Log.e(TAG, "camera2Listener is null, callback will not be called");
// }
if (previewDisplayView == null) {
throw new NullPointerException("you must preview on a textureView or a surfaceView");
}
if (maxPreviewSize != null && minPreviewSize != null) {
if (maxPreviewSize.x < minPreviewSize.x || maxPreviewSize.y < minPreviewSize.y) {
throw new IllegalArgumentException("maxPreviewSize must greater than minPreviewSize");
}
}
return new Camera2Helper(this);
}
}
}
然后 是Activity
package com.example.myapplication;
import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.TextureView;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewTreeObserver;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity implements ViewTreeObserver.OnGlobalLayoutListener {
private Camera2Helper camera2Helper;
private static final int ACTION_REQUEST_PERMISSIONS = 1;
private static final String CAMERA_ID = Camera2Helper.CAMERA_ID_BACK;
private TextureView textureView;
// 需要的权限
private static final String[] NEEDED_PERMISSIONS = new String[]{
Manifest.permission.CAMERA
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textureView = findViewById(R.id.texture_preview);
textureView.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == ACTION_REQUEST_PERMISSIONS) {
boolean isAllGranted = true;
for (int grantResult : grantResults) {
isAllGranted &= (grantResult == PackageManager.PERMISSION_GRANTED);
}
if (isAllGranted) {
camera2Helper.start();
} else {
Toast.makeText(this, "权限被拒绝", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onPause() {
if (camera2Helper != null) {
camera2Helper.stop();
}
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
if (camera2Helper != null) {
camera2Helper.start();
}
}
@Override
public void onGlobalLayout() {
textureView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
if (!checkPermissions(NEEDED_PERMISSIONS)) {
ActivityCompat.requestPermissions(this, NEEDED_PERMISSIONS, ACTION_REQUEST_PERMISSIONS);
Toast.makeText(this, "onGlobalLayout", Toast.LENGTH_SHORT).show();
} else {
initCamera();
}
}
private boolean checkPermissions(String[] neededPermissions) {
if (neededPermissions == null || neededPermissions.length == 0) {
return true;
}
boolean allGranted = true;
for (String neededPermission : neededPermissions) {
allGranted &= ContextCompat.checkSelfPermission(this, neededPermission) == PackageManager.PERMISSION_GRANTED;
}
return allGranted;
}
private void initCamera() {
camera2Helper = new Camera2Helper.Builder()
.maxPreviewSize(new Point(800, 600))//800*600 1920*1080
.minPreviewSize(new Point(640, 480))
.specificCameraId(CAMERA_ID)
.context(getApplicationContext())
.previewOn(textureView)
.previewViewSize(new Point(textureView.getWidth(), textureView.getHeight()))
.rotation(getWindowManager().getDefaultDisplay().getRotation())
.build();
camera2Helper.start();
}
}