主要概述相机基本知识,Android中相机基本知识点,自定义相机开发过程,相机开发总结内容。
相机API介绍
简单介绍下Android中CameraAPI版本历史
- 1.android.hardware.camera:最早用来自定义Camera的API
- 2.android.hardware.camera2:Android5.0之后推荐使用的API,对于Camera操作更灵活,功能更丰富
- 3.CameraX:jetpack库中的,对Camera2的封装,API更简单
Camera操作
由于Android的碎片化很严重,第一个API其实是兼容性比较好的了。下面就从使用第一个Camera的api开始讲起。
- 1.申请权限(Android6.0及以上)
- 2.打开相机,Camera.open()
- 3.打开预览,startPreview
- 4.停止预览,stopPreview
- 5.关闭相机,release()
打开相机
申请Camera权限,Android6.0及以上记得在打卡相机前进行动态申请
- 预览视图方式我们可以以三种方式进行,SurfaceView、TextureView、GLSurfaceView,所以我们肯定希望使用同一个管理对象去管理视图,那这个时候你能想到什么?第一个接口或者抽象类吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public interface ICameraManager {
void openCamera();
void closeCamera();
void startPreview(SurfaceHolder surfaceHolder);
void startPreview(SurfaceTexture surfaceTexture);
void stopPreview(); }
|
1.打开摄像头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class CameraManager implements ICameraManager {
...
public void openCamera(){ if(mCamera == null){ if(mCameraId >= Camera.getNumberOfCameras()){ onOpenError(CAMERA_ERROR_NO_ID,"NO Camera!"); return; } try { mCamera = Camera.open(mCameraId); Camera.getCameraInfo(mCameraId,mCameraInfo); initCamera(); onOpen(); mOrientationEventListener.enable() } catch (Exception e){ onOpenError(CAMERA_ERROR_OPEN,e.getMessage()); } } } }
|
打开摄像头之后我们可以设置摄像头的一些参数,如预览尺寸,拍照尺寸,fps,闪光灯等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
private void initCamera() { if (mCamera != null) { mParameters = mCamera.getParameters(); if (mDisplayOrientation == -1) { setCameraDisplayOrientation(mContext, mCameraId, mCamera); } mCamera.setDisplayOrientation(mDisplayOrientation); mParameters.setRotation(mOrientation);
List<String> supportedFlashModes = mParameters.getSupportedFlashModes(); if (supportedFlashModes != null && supportedFlashModes.contains(Parameters.FLASH_MODE_OFF)) { mParameters.setFlashMode(Parameters.FLASH_MODE_OFF); } List<String> supportedFocusModes = mParameters.getSupportedFocusModes(); if (supportedFocusModes != null && supportedFocusModes.contains(Parameters.FOCUS_MODE_AUTO)) { mParameters.setFocusMode(Parameters.FOCUS_MODE_AUTO); } mParameters.setPreviewFormat(ImageFormat.NV21); mParameters.setPictureFormat(ImageFormat.JPEG);
Camera.Size previewSize = getSuitableSize(mParameters.getSupportedPreviewSizes()); mPreviewWidth = previewSize.width; mPreviewHeight = previewSize.height; mPreviewSize = new Size(mPreviewWidth, mPreviewHeight); mParameters.setPreviewSize(mPreviewWidth, mPreviewHeight); Logs.d(TAG, "previewWidth: " + mPreviewWidth + ", previewHeight: " + mPreviewHeight);
Camera.Size pictureSize = mParameters.getPictureSize(); mParameters.setPictureSize(pictureSize.width, pictureSize.height); Logs.d(TAG, "pictureWidth: " + pictureSize.width + ", pictureHeight: " + pictureSize.height);
mCamera.setParameters(mParameters); isSupportZoom = mParameters.isSmoothZoomSupported(); } }
|
2.开始预览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
@Override public synchronized void startPreview(SurfaceHolder holder) { Logs.i(TAG, "startPreview..."); if (isPreviewing) { return; } if (mCamera != null) { try { mCamera.setPreviewDisplay(holder); if (!mPreviewBufferCallbacks.isEmpty()) { mCamera.addCallbackBuffer(new byte[mPreviewWidth * mPreviewHeight * 3 / 2]); mCamera.setPreviewCallbackWithBuffer(mPreviewCallback); } mCamera.startPreview(); onPreview(mPreviewWidth, mPreviewHeight); } catch (Exception e) { onPreviewError(CAMERA_ERROR_PREVIEW, e.getMessage()); } } }
|
3.停止预览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Override public synchronized void stopPreview() { Logs.v(TAG, "stopPreview."); if (isPreviewing && null != mCamera) { try { mCamera.setPreviewCallback(null); mCamera.stopPreview(); mPreviewBufferCallbacks.clear(); } catch (Exception e) { e.printStackTrace(); } } isPreviewing = false; }
|
4.关闭摄像头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Override public synchronized void releaseCamera() { Logs.v(TAG, "releaseCamera."); if (null != mCamera) { stopPreview(); try { mCamera.release(); mCamera = null; mCameraBytes = null; mDisplayOrientation = -1; } catch (Exception e) { } onClose(); } }
|
SurfaceView使用
我们要预览Camera数据必须要使用一个视图承接,SurfaceView是最常用的,也是Camera最初的标配。
1
| SurfaceView的特点:在自己独立的线程中绘制,内部使用双缓冲机制,画面更流畅。相比于TextureView,它内存占用低,绘制更及时,耗时也更低,但不支持动画和截图。
|
Camera预览需要将SurfaceHolder传递给Camera然后开启预览如何获取SurfaceHolder?
- 1.自定义CameraSurfaceView继承SurfaceView
- 2.实现SurfaceHolder.Callback接口,并在CameraSurfaceView初始化时设置回调
- 3.实现自定义CameraCallback接口,监听Camera状态
- 4.一定要实现onResume和onPause接口,并在对应的Activity生命周期中调用。这是所有使用Camera的bug源头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
| public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, CameraCallback {
private static final String TAG = CameraSurfaceView.class.getSimpleName(); SurfaceHolder mSurfaceHolder; private Context mContext; private Handler mHandler;
private boolean hasSurface; private CameraManager mCameraManager; private int mRatioWidth = 0; private int mRatioHeight = 0; private int mSurfaceWidth; private int mSurfaceHeight;
public CameraSurfaceView(Context context) { super(context); init(context); }
public CameraSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(context); }
public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); }
private void init(Context context) { mContext = context; mHandler = new Handler(context.getMainLooper()); mSurfaceHolder = getHolder(); mSurfaceHolder.setFormat(PixelFormat.TRANSPARENT); mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mSurfaceHolder.addCallback(this); mCameraManager = new CameraManager(context); mCameraManager.setCameraCallback(this); }
public CameraManager getCameraManager() { return mCameraManager; }
@Override public void surfaceCreated(SurfaceHolder holder) { Logs.i(TAG, "surfaceCreated..." + hasSurface); if (!hasSurface && holder != null) { hasSurface = true; openCamera(); } }
@Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Logs.i(TAG, "surfaceChanged [" + width + ", " + height + "]"); mSurfaceWidth = width; mSurfaceHeight = height; }
@Override public void surfaceDestroyed(SurfaceHolder holder) { Logs.v(TAG, "surfaceDestroyed."); closeCamera(); hasSurface = false; }
public SurfaceHolder getSurfaceHolder() { return mSurfaceHolder; }
public void onResume() { if (hasSurface) { openCamera(); } }
public void onPause() { closeCamera(); }
private void openCamera() { if (mSurfaceHolder == null) { Logs.e(TAG, "SurfaceHolder is null."); return; } if (mCameraManager.isOpen()) { Logs.w(TAG, "Camera is opened!"); return; } mCameraManager.openCamera(); }
private void closeCamera() { mCameraManager.releaseCamera(); }
private String getString(int resId) { return getResources().getString(resId); }
private 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); } } }
@Override public void onOpen() { mCameraManager.startPreview(getSurfaceHolder()); }
@Override public void onOpenError(int error, String msg) { }
@Override public void onPreview(int previewWidth, int previewHeight) { if (mSurfaceWidth > mSurfaceHeight) { setAspectRatio(previewWidth, previewHeight); } else { setAspectRatio(previewHeight, previewWidth); } }
@Override public void onPreviewError(int error, String msg) { }
@Override public void onClose() { } }
|
1.Camera操作时机
- 在surfaceCreated回调中打开Camera,在surfaceDestroyed中关闭摄像头,这基本上是所有Camera操作的常识,上面的代码中已经做了展示。
- 但这还不够,我们必须要把Camera的操作和生命周期进行绑定,在onResume中打开一次摄像头,onPause中关闭一次摄像头,确保SurfaceHolder不可用以及Activity不在前台时正确关闭Camera。
2.SurfaceView大小计算时机
- 在操作摄像头之前我们并不知道预览的尺寸,只能设置一个默认尺寸,最终预览尺寸需要等到openCamera之后,CameraCallback中提供了回调接口onPreview,在此,我们可以设想SurfaceView的大小比例来适配Camera预览尺寸,避免预览页面拉升或者压缩。
参考
自定义相机拍照,及调用系统相机 – 相机系列
Android Camera系列(一):SurfaceView+Camera