logo头像

勤求古训,博采众方

Android自定义相机开发(一)

主要概述相机基本知识,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();
}

  • CameraManager具体实现接口中的方法。

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
/**
* 配置Camera参数
*/
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
/**
* 使用Surfaceview开启预览
*
* @param holder
*/
@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
/**
* 停止预览,释放Camera
*/
@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);//translucent半透明 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) {
// 当activity暂停,但是并未停止的时候,surface仍然存在,所以 surfaceCreated()
// 并不会调用,需要在此处初始化摄像头
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