(注:Camera1在使用中的源码为android.hardware.Camera,这里方便做区分统一都写为Camer1)
下面是项目的源码地址,小伙伴们可根据需要自行取用:
Android Camer2与Camera1 自定义相机拍照封装实例
首先Camera1应该是我们刚开始接触Andorid 自定义相机的时候就会了解到的API了,因为Andorid系统的升级更新,从Android 5.0之后官方就基本推荐使用Camera2 API来实现自定义相机。关于Camera2与Camera1相对比的具体优势和实现细节,基本都在从我之前写的这篇文章中都有解答: Android Camera2相机使用流程讲解
这里我就直接开始代码封装的解读了,首先我们知道,使用Camera2 API必须是在Android 5.0之后,同时设备必须支持Camera2。所以一般情况下我们需要在针对使用Camera1还是使用Camera2需要做一个判断:只有Android 5.0及其以上设备,同时能够支持Camera2的设备。我们才可使用Cmaera2 API来实现自定义相机,否则就使用Camera1 API。
首先我做了一个CameraView通用接口,定义好相机使用过程中需要的通用的方法(拍照,切换相机等)。这样可以通过这个通用接口实现面向接口编程啦。
public interface CameraView {
/**
* 与生命周期onResume调用
*/
void onResume();
/**
* 与生命周期onPause调用
*/
void onPause();
/**
* 拍照
*/
void takePicture();
/**
* 拍照(有回调)
*/
void takePicture(TakePictureCallback takePictureCallback);
/**
* 设置保存的图片文件
*
* @param pictureSavePath 拍摄的图片返回的绝对路径
*/
void setPictureSavePath(String pictureSavePath);
/**
* 切换相机摄像头
*/
void switchCameraFacing();
interface TakePictureCallback {
void success(String picturePath);
void error(final String error);
}
}
接口中定义了onResume,onPause方法 因为相机需要通过这两个方法去判断启用还是暂停,通过Activity的回调的这两个生命周期。我们能够很好的去处理相机逻辑。其他的几个方法就是一般相机通用的方法啦。这里说一下拍照方法我加入了有回调和没回调方法,这个也是考虑到了业务逻辑的原因。
比如有时候我们只是想要调用拍照接口,然后让相机拍照后自动保存图片到文件即可,至于是否成功我们都可以不用考虑就调用无回调接口即可。
然后就是setPictureSavePath,设备保存的图片文件接口。通过此接口可以将相机拍照图片保存到我们指定的路径下,如果不设置这里我会保存在一个默认的路径下。
下面的代码分别就是对 Camera1 API 和 Camera2 API 实现的自定义View的核心代码部分了。
源码直接贴出来啦,其中对应API的解释也有,小伙伴们可以直接根据通过撸代码了解对应API啦。
Camera1 的显示View我是用到的SurfaceView。用过自定义相机的小伙伴应该对这个很熟悉了,另外有关Camera1的API网上也有很多。
我这里主要重点提一下使用相机的时候判断相机的旋转方向的方法,选择最佳的相机预览大小和最佳的拍照图片大小。
关于相机预览旋转方向,我这里用到了google官方推荐的方式,传入的参数CameraId就是相机面对方向(前置方向或后置方向)。对相机预览方向还有疑问的小伙伴可以看这里哦: Android相机预览方向深入探究
另外一点就是关于选择相机最佳预览方向,我这里也是使用了谷歌官方推荐的方案。
大致实现流程就是通过 Camerad的Parameters.getSupportedPreviewSizes() 获取相机预览支持的大小列表,通过Camerad的Parameters.getSupportedPictureSizes() 获取相机照片支持的大小列表。然后再根据我们相机的长宽比(比如4:3还是16:9),筛选出最大的符合长宽比的尺寸列表,然后再中这些符合条件的尺寸列表中选出最大的即可。
具体实现可下面的参考源码
public class Camera1Preview extends SurfaceView implements CameraView, SurfaceHolder.Callback {
private static final String TAG = "Camera1Preview";
private Camera mCamera;
private Context mContext;
private int mCameraCount;
private int mCurrentCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK;
private SurfaceHolder mSurfaceHolder;
/**
* 标识相机是否正在拍照过程中
*/
private final AtomicBoolean isPictureCaptureInProgress = new AtomicBoolean(false);
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public Camera1Preview(Context context) {
this(context, null);
}
public Camera1Preview(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Camera1Preview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
initCamera();
}
private void initCamera() {
mCamera = getCameraInstance();
if (mCamera == null)
return;
//得到摄像头数量
mCameraCount = Camera.getNumberOfCameras();
mSurfaceHolder = getHolder();
// 设置SurfaceHolder.Callback回调,这样我们可以在创建或销毁Surface时处理相应的逻辑
mSurfaceHolder.addCallback(this);
//设置屏幕常亮
mSurfaceHolder.setKeepScreenOn(true);
//点击自动对焦
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mCamera != null) {
mCamera.autoFocus(null);
}
}
});
}
@Override
public void onResume() {
if (mCamera == null)
mCamera = getCameraInstance();
}
@Override
public void onPause() {
releaseCamera();
}
/**
* 拍照方法(无回调,默认保存到文件中)
*/
@Override
public void takePicture() {
if (mCamera == null) {
throw new IllegalStateException(
"Camera is not ready. Call start() before takePicture().");
}
takePictureInternal();
}
/**
* 拍照方法(有回调)
*
* @param callback
*/
@Override
public void takePicture(TakePictureCallback callback) {
if (mCamera == null) {
throw new IllegalStateException(
"Camera is not ready. Call start() before takePicture().");
}
takePictureCallback = callback;
takePictureInternal();
}
private void takePictureInternal() {
//如果正在拍照处理中,则不能调用takePicture方法,否则应用会崩溃
if (!isPictureCaptureInProgress.get()) {
isPictureCaptureInProgress.set(true);
mCamera.takePicture(null, null, mPictureCallback);
}
}
/**
* 设置图片的保存路径
*
* @param pictureSavePath
*/
@Override
public void setPictureSavePath(String pictureSavePath) {
mPictureSaveDir = pictureSavePath;
}
/***
* 切换相机摄像头
*/
@Override
public void switchCameraFacing() {
if (mCameraCount > 1) {
mCurrentCameraFacing = (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) ?
Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK;
releaseCamera();
startPreview(mSurfaceHolder);
} else {
//手机不支持前置摄像头
}
}
/**
* 设置此视图的宽高比。
* 视图的大小将基于从参数计算的比率来测量。
* 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
/**
* 获取相机实例
*/
private Camera getCameraInstance() {
Camera camera = null;
try {
// 获取相机实例, 注意:某些设备厂商可能需要用 Camera.open() 方法才能打开相机。
camera = Camera.open(mCurrentCameraFacing);
} catch (Exception e) {
// 相机不可用或不存在
Log.e(TAG, "error open(int cameraId) camera1 : " + e.getMessage());
}
try {
if (null == camera)
camera = Camera.open();
} catch (Exception e) {
Log.e(TAG, "error open camera1() : " + e.getMessage());
}
return camera;
}
/**
* 释放相机
*/
private void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release(); // release the camera1 for other applications
mCamera = null;
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "surfaceCreated");
// Surface创建完成, 现在即可设置相机预览
startPreview(holder);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i(TAG, "surfaceChanged format" + format + ", width =" + width + " | height=" + height);
// surface在改变大小或旋转时触发此时间
// 确保在调整或重新格式化之前停止视频预览
if (mSurfaceHolder.getSurface() == null) {
// 预览Surface不存在
return;
}
// 改变前先停止预览
try {
mCamera.stopPreview();
startPreview(holder);
setCameraParameters(width, height);
} catch (Exception e) {
Log.d(TAG, "error starting camera1 preview: " + e.getMessage());
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "surfaceDestroyed");
//释放相机
releaseCamera();
}
private void startPreview(SurfaceHolder holder) {
if (mCamera == null)
mCamera = getCameraInstance();
try {
mCamera.setPreviewDisplay(holder);
//设置预览的旋转角度
mCamera.setDisplayOrientation(calcDisplayOrientation(mCurrentCameraFacing));
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "error setting camera1 preview:" + e.getMessage());
}
}
private int calcDisplayOrientation(int cameraId) {
Camera.CameraInfo info =
new Camera.CameraInfo();
Camera.getCameraInfo(cameraId, info);
int rotation = ((Activity) mContext).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;
}
mDisplayOrientation = degrees;
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;
}
return result;
}
private Camera.Parameters mCameraParameters;
private final SizeMap mPreviewSizes = new SizeMap();
private final SizeMap mPictureSizes = new SizeMap();
private AspectRatio mAspectRatio;
private int mDisplayOrientation;
private AspectRatio chooseAspectRatio() {
AspectRatio r = null;
for (AspectRatio ratio : mPreviewSizes.ratios()) {
r = ratio;
if (ratio.equals(Constants.DEFAULT_ASPECT_RATIO)) {
return ratio;
}
}
return r;
}
private Size chooseOptimalSize(SortedSet sizes, int surfaceWidth, int surfaceHeight) {
int desiredWidth;
int desiredHeight;
if (isLandscape(mDisplayOrientation)) {
desiredWidth = surfaceHeight;
desiredHeight = surfaceWidth;
} else {
desiredWidth = surfaceWidth;
desiredHeight = surfaceHeight;
}
Size result = null;
for (Size size : sizes) { // Iterate from small to large
if (desiredWidth <= size.getWidth() && desiredHeight <= size.getHeight()) {
return size;
}
result = size;
}
return result;
}
/**
* Test if the supplied orientation is in landscape.
*
* @param orientationDegrees Orientation in degrees (0,90,180,270)
* @return True if in landscape, false if portrait
*/
private boolean isLandscape(int orientationDegrees) {
return (orientationDegrees == Constants.LANDSCAPE_90 ||
orientationDegrees == Constants.LANDSCAPE_270);
}
/***
* 设置相机参数
*
* @param width
* @param height
*/
private void setCameraParameters(int width, int height) {
if (mCamera == null)
return;
mCameraParameters = mCamera.getParameters();
// 相机预览支持的大小
mPreviewSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPreviewSizes()) {
mPreviewSizes.add(new Size(size.width, size.height));
}
// 相机照片支持的大小
mPictureSizes.clear();
for (Camera.Size size : mCameraParameters.getSupportedPictureSizes()) {
mPictureSizes.add(new Size(size.width, size.height));
}
// AspectRatio 默认长宽比为4:3
if (mAspectRatio == null) {
mAspectRatio = Constants.DEFAULT_ASPECT_RATIO;
}
SortedSet sizes = mPreviewSizes.sizes(mAspectRatio);
if (sizes == null) { // Not supported
mAspectRatio = chooseAspectRatio();
sizes = mPreviewSizes.sizes(mAspectRatio);
}
Size previewSize = chooseOptimalSize(sizes, width, height);
// Always re-apply camera1 parameters
// Largest picture size in this ratio
final Size pictureSize = mPictureSizes.sizes(mAspectRatio).last();
mCameraParameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
mCameraParameters.setPictureSize(pictureSize.getWidth(), pictureSize.getHeight());
mCameraParameters.setPictureFormat(ImageFormat.JPEG); // 设置图片格式
mCameraParameters.setJpegQuality(100); // 设置照片质量
mCameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦
//parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);//连续对焦
//camera1.cancelAutoFocus();//如果要实现连续的自动对焦,这一句必须加上
mCamera.setParameters(mCameraParameters);
//根据我们选中的预览相机大小的宽高比调整View的大小
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setAspectRatio(
previewSize.getWidth(), previewSize.getHeight());
} else {
setAspectRatio(
previewSize.getHeight(), previewSize.getWidth());
}
}
private String mPictureSaveDir;
private TakePictureCallback takePictureCallback;
private Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(final byte[] data, Camera camera) {
Log.d(TAG, "onPictureTaken start timestemp :" + System.currentTimeMillis());
savePictureToSDCard(data);
startPreview(mSurfaceHolder);
isPictureCaptureInProgress.set(false);
Log.d(TAG, "onPictureTaken end timestemp :" + System.currentTimeMillis());
}
};
/**
* 将拍下来的照片存放在SD卡中
*
* @param data
*/
private void savePictureToSDCard(byte[] data) {
File pictureFile;
//检测外部存储是否存在
if (FileUtils.checkSDCard()) {
if (mPictureSaveDir == null) {
pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE);
} else {
pictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE);
}
if (pictureFile == null) {
Log.e(TAG, "error creating media file, check storage permissions");
if (takePictureCallback != null) {
takePictureCallback.error("error creating media file, check storage permissions");
}
return;
}
} else {
pictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE);
}
try {
FileOutputStream outputStream = new FileOutputStream(pictureFile);
//由于在预览的时候,我们调整了预览的方向,所以在保存的时候我们要旋转回来,不然保存的图片方向是不正确的
Matrix matrix = new Matrix();
if (mCurrentCameraFacing == Camera.CameraInfo.CAMERA_FACING_BACK) {
matrix.setRotate(90);
} else {
matrix.setRotate(-90);
}
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
outputStream.write(data);
outputStream.close();
} catch (Exception e) {
Log.e(TAG, "savePictureToSDCard error :" + e.getMessage());
if (takePictureCallback != null) {
takePictureCallback.error(e.getMessage());
}
return;
}
if (takePictureCallback != null)
takePictureCallback.success(pictureFile.getAbsolutePath());
//这个的作用是让系统去扫描刚拍下的这个图片文件,以利于在MediaSore中能及时更新,
// 可能会存在部分手机不用使用的情况(众所周知,现在国内的Rom厂商已把原生Rom改的面目全非)
//mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + mPictureSavePath)));
// MediaScannerConnection.scanFile(mContext, new String[]{
// pictureFile.getAbsolutePath()},
// null, new MediaScannerConnection.OnScanCompletedListener() {
// @Override
// public void onScanCompleted(String path, Uri uri) {
//// Log.e(TAG, "扫描完成");
// }
// });
}
}
Camera2的封装这里我用了TextureView 。其中有关Camera2 API的具体的使用方式和解读可在我的这一篇博文中找到: Android Camera2相机使用流程讲解
这里我就直接贴出源码啦。
public class Camera2Preview extends TextureView implements CameraView {
private static final String TAG = "Camera2Preview";
private Context mContext;
private WindowManager mWindowManager;
private int mRatioWidth = 0;
private int mRatioHeight = 0;
private int mCameraCount;
private int mCurrentCameraFacing = CameraCharacteristics.LENS_FACING_BACK;
public Camera2Preview(Context context) {
this(context, null);
}
public Camera2Preview(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Camera2Preview(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
private void init() {
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}
/**
* 同生命周期 onResume
*/
@Override
public void onResume() {
startBackgroundThread();
// 当关闭并打开屏幕时,SurfaceTexture已经可用,并且不会调用“onSurfaceTextureAvailable”。
// 在这种情况下,我们可以打开相机并从这里开始预览(否则,我们将等待,直到Surface在SurfaceTextureListener中准备好)。
if (isAvailable()) {
openCamera(getWidth(), getHeight());
} else {
setSurfaceTextureListener(mSurfaceTextureListener);
}
}
/**
* 同生命周期 onPause
*/
@Override
public void onPause() {
closeCamera();
stopBackgroundThread();
}
/**
* 启动拍照的方法
*/
@Override
public void takePicture() {
takePictureInternal();
}
private TakePictureCallback mTakePictureCallback;
@Override
public void takePicture(TakePictureCallback takePictureCallback) {
mTakePictureCallback = takePictureCallback;
takePictureInternal();
}
private void takePictureInternal() {
if (mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK) {
lockFocus();
} else {
runPrecaptureSequence();
}
}
/**
* 设置图片的保存路径(文件夹)
*
* @param pictureSavePath
*/
private String mPictureSaveDir;
@Override
public void setPictureSavePath(String pictureSavePath) {
mPictureSaveDir = pictureSavePath;
}
@Override
public void switchCameraFacing() {
if (mCameraCount > 1) {
mCurrentCameraFacing = mCurrentCameraFacing == CameraCharacteristics.LENS_FACING_BACK ?
CameraCharacteristics.LENS_FACING_FRONT : CameraCharacteristics.LENS_FACING_BACK;
closeCamera();
openCamera(getWidth(), getHeight());
}
}
/**
* 设置此视图的宽高比。
* 视图的大小将基于从参数计算的比率来测量。
* 请注意,参数的实际大小并不重要,也就是说,setAspectRatio(2, 3)setAspectRatio(4, 6)会得到相同的结果。
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
/**
* 相机状态: 显示相机预览
*/
private static final int STATE_PREVIEW = 0;
/**
* 相机状态: 等待焦点被锁定
*/
private static final int STATE_WAITING_LOCK = 1;
/**
* 相机状态: 等待曝光前的状态。
*/
private static final int STATE_WAITING_PRECAPTURE = 2;
/**
* 相机状态: 等待曝光状态非预先捕获的东西.
*/
private static final int STATE_WAITING_NON_PRECAPTURE = 3;
/**
* 相机状态: 照片拍摄
*/
private static final int STATE_PICTURE_TAKEN = 4;
/**
* 最大的预览宽度
*/
private static final int MAX_PREVIEW_WIDTH = 1920;
/**
* 最大的预览高度
*/
private static final int MAX_PREVIEW_HEIGHT = 1080;
/**
* {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a
* {@link TextureView}.
*/
private final TextureView.SurfaceTextureListener mSurfaceTextureListener
= new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture texture) {
}
};
/**
* ID of the current {@link CameraDevice}.
*/
private String mCameraId;
/**
* 相机预览要用到的{@link CameraCaptureSession } .
*/
private CameraCaptureSession mCaptureSession;
/**
* A reference to the opened {@link CameraDevice}.
*/
private CameraDevice mCameraDevice;
/**
* 相机预览的 {@link android.util.Size}
*/
private Size mPreviewSize;
/**
* {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its state.
*/
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(@NonNull CameraDevice cameraDevice) {
// 当相机打开时调用此方法。我们在这里开始相机预览。
mCameraOpenCloseLock.release();
mCameraDevice = cameraDevice;
createCameraPreviewSession();
}
@Override
public void onDisconnected(@NonNull CameraDevice cameraDevice) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
}
@Override
public void onError(@NonNull CameraDevice cameraDevice, int error) {
mCameraOpenCloseLock.release();
cameraDevice.close();
mCameraDevice = null;
Log.e(TAG, "CameraDevice.StateCallback onError errorCode= " + error);
}
};
/**
* 用于运行不应该阻止UI的任务的附加线程。
*/
private HandlerThread mBackgroundThread;
/**
* 在后台运行任务的Handler。
*/
private Handler mBackgroundHandler;
/**
* An {@link ImageReader} 用于处理图像捕获(抓拍).
*/
private ImageReader mImageReader;
/**
* 图片文件
*/
private File mPictureFile;
/**
* 这是{@link ImageReader}的回调对象. 当静态图像准备好保存时,将回调"onImageAvailable"
*/
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
//检测外部存储是否存在
//根据时间戳生成图片名称
if (FileUtils.checkSDCard()) {
if (mPictureSaveDir == null) {
mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE);
} else {
mPictureFile = FileUtils.getTimeStampMediaFile(mPictureSaveDir, FileUtils.MEDIA_TYPE_IMAGE);
}
if (mPictureFile == null) {
Log.e(TAG, "error creating media file, check storage permissions");
if (mTakePictureCallback != null) {
mTakePictureCallback.error("error creating media file, check storage permissions");
}
return;
}
} else {
mPictureFile = FileUtils.getOutputMediaFile(mContext, FileUtils.MEDIA_TYPE_IMAGE);
}
mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(), mPictureFile));
}
};
/**
* {@link CaptureRequest.Builder} for the camera1 preview
*/
private CaptureRequest.Builder mPreviewRequestBuilder;
/**
* {@link CaptureRequest} generated by {@link #mPreviewRequestBuilder}
*/
private CaptureRequest mPreviewRequest;
/**
* 拍照相机的当前状态
*
* @see #mCaptureCallback
*/
private int mState = STATE_PREVIEW;
/**
* A {@link Semaphore} 防止相机关闭前退出应用程序。
*/
private Semaphore mCameraOpenCloseLock = new Semaphore(1);
/**
* 当前的相机设备是否支持Flash。
*/
private boolean mFlashSupported;
/**
* 相机传感器的方向
*/
private int mSensorOrientation;
/**
* A {@link CameraCaptureSession.CaptureCallback} 处理JPEG捕获的相关事件
*/
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
// We have nothing to do when the camera1 preview is working normally.
break;
}
case STATE_WAITING_LOCK: {
Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (afState == null) {
captureStillPicture();
} else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
} else {
runPrecaptureSequence();
}
}
break;
}
case STATE_WAITING_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null ||
aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
mState = STATE_WAITING_NON_PRECAPTURE;
}
break;
}
case STATE_WAITING_NON_PRECAPTURE: {
// CONTROL_AE_STATE can be null on some devices
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
mState = STATE_PICTURE_TAKEN;
captureStillPicture();
}
break;
}
}
}
@Override
public void onCaptureProgressed(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull CaptureResult partialResult) {
process(partialResult);
}
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
process(result);
}
};
/**
* 给定相机支持的{@code Size}的{@code choices},
* 选择至少与相应TextureView大小一样大、最多与相应最大大小一样大、且纵横比与指定值匹配的最小一个。
* 如果不存在这样的尺寸,则选择最大尺寸与相应的最大尺寸一样大,并且其纵横比与指定值匹配的最大尺寸。
*
* @param choices 摄像机支持预期输出类的大小列表。
* @param textureViewWidth TextureView相对于传感器坐标的宽度
* @param textureViewHeight TextureView相对于传感器坐标的高度
* @param maxWidth 可选择的最大宽度
* @param maxHeight 可选择的最大高度
* @param aspectRatio 宽高比
* @return 最佳的 {@code Size}, 或任意一个,如果没有足够大的话
*/
private static Size chooseOptimalSize(Size[] choices, int textureViewWidth,
int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
// 收集至少与预览表面一样大的支持分辨率。
List bigEnough = new ArrayList<>();
// 收集小于预览表面的支持的分辨率
List notBigEnough = new ArrayList<>();
int w = aspectRatio.getWidth();
int h = aspectRatio.getHeight();
for (Size option : choices) {
if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight &&
option.getHeight() == option.getWidth() * h / w) {
if (option.getWidth() >= textureViewWidth &&
option.getHeight() >= textureViewHeight) {
bigEnough.add(option);
} else {
notBigEnough.add(option);
}
}
}
// 挑那些最小中的足够大的。如果没有足够大的,选择最大的那些不够大的。
if (bigEnough.size() > 0) {
return Collections.min(bigEnough, new CompareSizesByArea());
} else if (notBigEnough.size() > 0) {
return Collections.max(notBigEnough, new CompareSizesByArea());
} else {
Log.e(TAG, "Couldn't find any suitable preview size");
return choices[0];
}
}
/**
* 设置与摄像机相关的成员变量。
*
* @param width 相机预览可用尺寸的宽度
* @param height 相机预览可用尺寸的高度
*/
@SuppressWarnings("SuspiciousNameCombination")
private void setUpCameraOutputs(int width, int height) {
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
String[] cameraIdList = manager.getCameraIdList();
mCameraCount = cameraIdList.length;
for (String cameraId : cameraIdList) {
CameraCharacteristics characteristics
= manager.getCameraCharacteristics(cameraId);
//判断当前摄像头是前置还是后置摄像头
Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing != mCurrentCameraFacing) {
continue;
}
StreamConfigurationMap map = characteristics.get(
CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
if (map == null) {
continue;
}
// 对于静态图像捕获,我们使用最大可用的大小。
Size largest = Collections.max(
Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
new CompareSizesByArea());
mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(),
ImageFormat.JPEG, /*maxImages*/2);
mImageReader.setOnImageAvailableListener(
mOnImageAvailableListener, mBackgroundHandler);
// 找出是否需要交换尺寸以获得相对与传感器坐标的预览大小
int displayRotation = mWindowManager.getDefaultDisplay().getRotation();
//检验条件
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
boolean swappedDimensions = false;
switch (displayRotation) {
case Surface.ROTATION_0:
case Surface.ROTATION_180:
if (mSensorOrientation == 90 || mSensorOrientation == 270) {
swappedDimensions = true;
}
break;
case Surface.ROTATION_90:
case Surface.ROTATION_270:
if (mSensorOrientation == 0 || mSensorOrientation == 180) {
swappedDimensions = true;
}
break;
default:
Log.e(TAG, "Display rotation is invalid: " + displayRotation);
}
Point displaySize = new Point();
mWindowManager.getDefaultDisplay().getSize(displaySize);
int rotatedPreviewWidth = width;
int rotatedPreviewHeight = height;
int maxPreviewWidth = displaySize.x;
int maxPreviewHeight = displaySize.y;
if (swappedDimensions) {
rotatedPreviewWidth = height;
rotatedPreviewHeight = width;
maxPreviewWidth = displaySize.y;
maxPreviewHeight = displaySize.x;
}
if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
maxPreviewWidth = MAX_PREVIEW_WIDTH;
}
if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
maxPreviewHeight = MAX_PREVIEW_HEIGHT;
}
// 危险,W.R.!尝试使用太大的预览大小可能超过相机总线的带宽限制,导致高清的预览,但存储垃圾捕获数据。
mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class),
rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
maxPreviewHeight, largest);
// 我们将TextureView的宽高比与我们选择的预览大小相匹配。
int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
setAspectRatio(
mPreviewSize.getWidth(), mPreviewSize.getHeight());
} else {
setAspectRatio(
mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
//检验是否支持flash
Boolean available = characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
mFlashSupported = available == null ? false : available;
mCameraId = cameraId;
return;
}
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (NullPointerException e) {
//抛出空指针一般代表当前设备不支持Camera2API
Log.e(TAG, "This device doesn't support Camera2 API.");
}
}
/**
* 打开指定的相机(mCameraId)
*/
private void openCamera(int width, int height) {
setUpCameraOutputs(width, height);
configureTransform(width, height);
CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera1 opening.");
}
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera1 opening.", e);
}
}
/**
* 关闭相机
*/
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;
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera1 closing.", e);
} finally {
mCameraOpenCloseLock.release();
}
}
/**
* 启动后台线程和Handler.
*/
private void startBackgroundThread() {
mBackgroundThread = new HandlerThread("CameraBackground");
mBackgroundThread.start();
mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
}
/**
* 停止后台线程和Handler.
*/
private void stopBackgroundThread() {
mBackgroundThread.quitSafely();
try {
mBackgroundThread.join();
mBackgroundThread = null;
mBackgroundHandler = null;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建一个新的 {@link CameraCaptureSession} 用于相机预览.
*/
private void createCameraPreviewSession() {
try {
SurfaceTexture texture = getSurfaceTexture();
assert texture != null;
// 我们将默认缓冲区的大小设置为我们想要的相机预览的大小。
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
// 我们需要开始预览输出Surface
Surface surface = new Surface(texture);
// 我们建立了一个具有输出Surface的捕获器。
mPreviewRequestBuilder
= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
mPreviewRequestBuilder.addTarget(surface);
// 这里,我们创建了一个用于相机预览的CameraCaptureSession
mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),
new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
// 相机已经关闭
if (null == mCameraDevice) {
return;
}
// 当session准备好后,我们开始显示预览
mCaptureSession = cameraCaptureSession;
try {
// 相机预览时应连续自动对焦
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 设置闪光灯在必要时自动打开
setAutoFlash(mPreviewRequestBuilder);
// 最终,显示相机预览
mPreviewRequest = mPreviewRequestBuilder.build();
mCaptureSession.setRepeatingRequest(mPreviewRequest,
mCaptureCallback, mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(
@NonNull CameraCaptureSession cameraCaptureSession) {
Log.e(TAG, "CameraCaptureSession.StateCallback onConfigureFailed");
}
}, null
);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 配置必要的 {@link android.graphics.Matrix} 转换为 `mTextureView`.
*
* 该方法应该在setUpCameraOutputs中确定相机预览大小以及“mTextureView”的大小固定之后调用。
*
* @param viewWidth The width of `mTextureView`
* @param viewHeight The height of `mTextureView`
*/
private void configureTransform(int viewWidth, int viewHeight) {
if (null == mPreviewSize || null == mContext) {
return;
}
int rotation = mWindowManager.getDefaultDisplay().getRotation();
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), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
setTransform(matrix);
}
/**
* 锁定焦点作为静态图像捕获的第一步
*/
private void lockFocus() {
try {
// 这里是让相机锁定焦点
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_START);
// 告知 #mCaptureCallback 等待锁
mState = STATE_WAITING_LOCK;
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 运行预捕获序列捕获一张静态图片。
*
* 这个方法应该在我们从得到mCaptureCallback的响应后调用
*/
private void runPrecaptureSequence() {
try {
// 这就是如何告诉相机触发。
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
// 告知 #mCaptureCallback 等待设置预捕获序列。
mState = STATE_WAITING_PRECAPTURE;
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 捕获一张静态图片
* 这个方法应该在我们从得到mCaptureCallback的响应后调用
*/
private void captureStillPicture() {
try {
if (null == mCameraDevice) {
return;
}
// 这是 CaptureRequest.Builder ,我们用它来进行拍照
final CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// 使用相同的AE和AF模式作为预览。
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
setAutoFlash(captureBuilder);
// 方向
int rotation = mWindowManager.getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback CaptureCallback
= new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session,
@NonNull CaptureRequest request,
@NonNull TotalCaptureResult result) {
Log.d(TAG, "CameraCaptureSession.CaptureCallback onCaptureCompleted 图片保存地址为:" + mPictureFile.toString());
if (mTakePictureCallback != null) {
mTakePictureCallback.success(mPictureFile.getAbsolutePath());
}
unlockFocus();
}
};
mCaptureSession.stopRepeating();
mCaptureSession.abortCaptures();
mCaptureSession.capture(captureBuilder.build(), CaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**
* 从指定的屏幕旋转中检索JPEG方向。
*
* @param rotation 图片旋转
* @return The JPEG orientation (one of 0, 90, 270, and 360)
*/
private int getOrientation(int rotation) {
// 对于大多数设备,传感器定向是90,对于某些设备(例如Nexus 5X)是270。
//我们必须考虑到这一点,并适当的旋转JPEG。
//对于取向为90的设备,我们只需从方向返回映射即可。
//对于方向为270的设备,我们需要旋转JPEG 180度。
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}
/**
* 解锁焦点.
*
* 此方法应该在静态图片捕获序列结束后调用
*/
private void unlockFocus() {
try {
// 重置自动对焦触发
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
setAutoFlash(mPreviewRequestBuilder);
mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback,
mBackgroundHandler);
// 在此之后,相机将回到正常的预览状态。
mState = STATE_PREVIEW;
mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback,
mBackgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void setAutoFlash(CaptureRequest.Builder requestBuilder) {
if (mFlashSupported) {
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
}
/**
* 将JPEG{@link Image}保存并放到指定的文件中
*/
private static class ImageSaver implements Runnable {
/**
* The JPEG image
*/
private final Image mImage;
/**
* The file we save the image into.
*/
private final File mFile;
ImageSaver(Image image, File file) {
mImage = image;
mFile = file;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
FileOutputStream output = null;
try {
output = new FileOutputStream(mFile);
output.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImage.close();
if (null != output) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 根据它们的区域比较两个的大小 {@code Size}。
*/
static class CompareSizesByArea implements Comparator {
@Override
public int compare(Size lhs, Size rhs) {
// We cast here to ensure the multiplications won't overflow
return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
(long) rhs.getWidth() * rhs.getHeight());
}
}
好了,Camera1和Camera2 我们已经用自定义View封装好了,其中用到的一些工具类方法小伙伴们可以直接从我的github源码中取得咯。而博客中我更想要分享的是我在封装代码中收获到的一些心得。
在封装相机API的过程中,我主要是从业务使用方式的简便性出发封装的Camera API。这里我也只是封装了相机的拍照功能,后续也可以加入相机的录像功能。
欢迎小伙伴们提出自己的想法和意见,我们可以互相交流共同进步。