Camera2 介绍
从android5.0开始对相机的API进行了全新的设计,这些API都在android.hardware.camera2包下,也是为了区分之前的API,新API不仅大幅提高了Android系统拍照等功能,还支持RAW照片输出,甚至允许程序调整相机的对焦模式、曝光模式、快门等。官网给出的说明如下:
Camera2 API
- Supports 30fps full resolution with burst mode
支持30fps的全高清连拍 - Supports change on manual camera settings between frame capture
支持帧之间的手动设置 - Supports RAW image capture
支持RAW格式的图片拍摄 - Supports Zero Shutter Lag & Movie Snapshot
支持快门0延迟以及电影速拍 - Supports setting other manual camera device controls including level of Noise Cancelling
支持相机其他方面的手动控制包括噪音消除的级别
Camera2工作流程示意图:
Google采用了pipeline(管道)的概念,将Camera Device相机设备和Android Device安卓设备连接起来, Android Device通过管道发送CaptureRequest拍照请求给Camera Device,Camera Device通过管道返回CameraMetadata数据给Android Device,这一切建立在一个叫作CameraCaptureSession的会话中。Camera2类图:
其中CameraManager是所有相机设备(CameraDevice)的管理者,而每个CameraDevice自己会负责建立CameraCaptureSession以及建立CaptureRequest。CameraCharacteristics是CameraDevice的属性描述类,在CameraCharacteristics中可以进行相机设备功能的详细设定(当然了,首先你得确定你的相机设备支持这些功能才行)类图中有着三个重要的callback,其中CameraCaptureSession.CaptureCallback将处理预览和拍照图片的工作,需要重点对待。
以上的类是如何相互配合的?下面是拍照的流程图:
- 调用openCamera方法后会回调CameraDevice.StateCallback方法,该方法重写onPened函数;
- 在onOpened方法中调用createCaptureSession方法,该方法又回调CameraCaptureSession.StateCallback方法。
- 在CameraCaptureSession.StateCallback中重写onConfigured方法,设置setRepeatingRequest方法(也即开启预览);
- 在setRepeatingRequest又会回调CameraCaptureSession.CaptureCallback方法;
- 重写CameraCaptureSession.CaptureCallback中的onCaptureCompleted方法,result就是未经过处理的元数据;
其中onCaptureProgressed方法很明显是在Capture过程中的,也就是在onCaptureCompleted之前,所以,在这之前相对图像干什么就看你的了,像美颜等操作就可以在这个方法中实现了。
由此也可以看出Camera2相机使用还是简单的,其实就是3个Callback函数的回调,先说一下:setRepeatingRequest和capture方法其实都是向相机设备发送获取图像的请求,但是capture就获取那么一次,而setRepeatingRequest就是不停的获取图像数据,所以呢,使用capture就想拍照一样,图像就停在那里了,但是setRepeatingRequest一直在发送和获取,所以需要连拍的时候就调用它,然后在onCaptureCompleted中保存图像就行了。(注意了,图像的预览也是用的setRepeatingRequest,只要不处理数据就行了),从上面我们也了解到Camera2运用主要用到这些类:CameraManager、CameraDevice、CameraCharacteristics、CameraRequest与CameraRequest.Builder、CameraCaptureSession以及CaptureResult。
CameraManager:位于android.hardware.camera2.CameraManager下,也是Android 21(5.0)添加的,和其他系统服务一样通过 Context.getSystemService(CameraManager.class ) 或者Context.getSystemService(Context.CAMERA_SERVICE) 来完成初始化,主要用于管理系统摄像头:
- 通过getCameraIdList()方法获取Android设备的摄像头列表,如今手机基本都有前后摄像头,有的手机则更多;
- 通过getCameraIdList()方法获取Android设备的摄像头列表;
- openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)打开指定Id的摄像头;
CameraDevice:CameraDevice是Camera2中抽象出来的一个对象,直接与系统硬件摄像头相联系。因为不可能所有的摄像头都会支持高级功能(即摄像头功能可被分为limit 和full 两个级别),当摄像头处于limited 级别时候,此时Camera2和早期的Camera功能差不多,除此之外在Camera2架构中,CameraDevice还承担其他两项重要任务:
- 通过CameraDevice.StateCallback监听摄像头的状态(主要包括onOpened、onClosed、onDisconnected、onErro四种状态);
- 管理CameraCaptureSession,通过方法createCaptureSession(List
outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法和createReprocessableCaptureSession(InputConfiguration inputConfig, List outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法创建会话 (其中第三个参数: The handler on which the callback should be invoked, or null to use the current thread’s looper.),通常会在CameraDevice.StateCallback中调用对应方法创建预览会话。 - 管理CaptureRequest,主要包括通过createCaptureRequest(int templateType)创建捕获请求,在需要预览、拍照、再次预览的时候都需要通过创建请求来完成;
CameraCaptureSession:系统向摄像头发送 Capture 请求,而摄像头会返回 CameraMetadata,这一切都是在由对应的 CameraDevice创建的CameraCaptureSession 会话 完成,当程序需要预览、拍照、再次预览时,都需要先通过会话。(A configured capture session for a CameraDevice, used for capturing images from the camera or reprocessing images captured from the camera in the same session previously.A CameraCaptureSession is created by providing a set of target output surfaces to createCaptureSession, or by providing an InputConfiguration and a set of target output surfaces to createReprocessableCaptureSession for a reprocessable capture session. Once created, the session is active until a new session is created by the camera device, or the camera device is closed.)CameraCaptureSession一旦被创建,直到对应的CameraDevice关闭才会死掉。虽然CameraCaptureSession会话用于从摄像头中捕获图像,但是 只有同一个会话才能再次从同一摄像头中捕获图像。另外,创建会话是一项耗时的异步操作,可能需要几百毫秒,因为它需要配置相机设备的内部管道并分配内存缓冲区以将图像发送到所需的目标,因而createCaptureSession和createReprocessableCaptureSession会将随时可用的CameraCaptureSession发送到提供的监听器的onConfigured回调中。如果无法完成配置,则触发onConfigureFailed回调,并且会话将不会变为活动状态。最后需要注意的是,如果摄像头设备创建了一个新的会话,那么上一个会话是被关闭的,并且会回调与其关联的onClosed,如果不处理好,当会话关闭之后再次调用会话的对应方法那么所有方法将会跑出IllegalStateException异常。关闭的会话清除任何重复的请求(和调用了stopRepeating()方法类似),但是在新创建的会话接管并重新配置摄像机设备之前,关闭的会话仍然会正常完成所有正在进行的捕获请求。简而言之,在Camera2中CameraCaptureSession承担很重要的角色:
- 管理CameraCaptureSession.StateCallback状态回调,用于接收有关CameraCaptureSession状态的更新的回调对象,主要回调方法有两个当CameraDevice 完成配置,对应的会话开始处理捕获请求时触发onConfigured(CameraCaptureSession session)方法,反之配置失败时候触发onConfigureFailed(CameraCaptureSession session)方法;
- 管理CameraCaptureSession.CaptureCallback捕获回调,用于接收捕获请求状态的回调,当请求触发捕获已启动时;捕获完成时;在捕获图像时发生错误的情况下;都会触发该回调对应的方法;
- 通过调用方法capture(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)提交捕获图像请求(Submit a request for an image to be captured by the camera device.)即拍照,其中该请求定义了捕获单个图像的所有参数,包括传感器,镜头,闪光灯和后处理参数,每一次请求的结果将产生一个CaptureResult,可以为一个或多个Surface生成新的帧,然后 通过CaptureRequest.Builder的addTarget(Surface)方法附着到对应的Surface上显示,而且这个参数Surface必须是会话创建时候的一个子集,会话一次可以处理多个常规和重新处理请求。但如果只有常规请求或重新处理请求在进行,则以先进先出的顺序处理它们;如果两者都在进行中则分别以各自的先进先出顺序处理他们;然而,处理常规请求和重新处理请求的顺序并不是特定的,换言之,一个常规请求在下一个常规请求提交前被处理,同理重新处理请求也一样,但是一个常规请求不一定是在下一个重新处理请求提交之前被处理。通过capture方法提交的请求处理优先级比通过其他方式( setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler) 或者setRepeatingBurst(List, CameraCaptureSession.CaptureCallback, Handler))提交的请求的处理优先级高,一旦当前的repeat / repeatBurst处理完成,就会被处理。最后一点,所有CaptureSession可用于从相机捕获图像,但只有由createReprocessableCaptureSession创建的会话才可以提交重新处理捕获请求,
将重新处理请求提交到常规捕获会话将导致IllegalArgumentException; - 通过调用方法setRepeatingRequest(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)请求不断重复捕获图像,即实现预览;
- 通过方法调用stopRepeating()实现停止捕获图像,即停止预览;
CameraCharacteristics:描述Cameradevice属性的对象,可以使用CameraManager通过getCameraCharacteristics(String cameraId)进行查询;
CameraRequest和CameraRequest.Builder:CameraRequest代表了一次捕获请求,而 CameraRequest.Builder用于描述捕获图片的各种参数设置,包含捕获硬件(传感器,镜头,闪存),对焦模式、曝光模式,处理流水线,控制算法和输出缓冲区的配置。,然后传递到对应的会话中进行设置,CameraRequest.Builder则负责生成CameraRequest对象。当程序调用setRepeatingRequest()方法进行预览时,或调用capture()方法进行拍照时,都需要传入CameraRequest参数。CameraRequest可以通过CameraRequest.Builder来进行初始化,通过调用createCaptureRequest来获得;
CaptureResult:CaptureRequest描述是从图像传感器捕获单个图像的结果的子集的对象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)当CaptureRequest被处理之后由CameraDevice生成;
Camera2 主要角色之间的联系:CameraManager处于顶层管理位置负责检测获取所有摄像头及其特性和传入指定的CameraDevice.StateCallback回调打开指定摄像头,CameraDevice 是负责管理抽象对象,包括 监听Camera 的状态回调CameraDevice.StateCallback、创建CameraCaptureSession和CameraRequest,CameraCaptureSession 用于描述一次图像捕获操作,主要负责 监听自己会话的状态回调CameraCaptureSession.StateCallback和CameraCaptureSession.CaptureCallback捕获回调,还有发送处理 CameraRequest;CameraRequest 则可以看成是一个”JavaBean”的作用用于描述希望什么样的配置来处理这次请求;最后三个回调用于监听对应的状态。
Camera2 使用
- 权限配置:
1
2
3
4
5
6//相机权限
<uses-permission android:name="android.permission.CAMERA" />
//保存照片需要的权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
//录制视频需要的权限
<uses-permission android:name="android.permission.RECORD_AUDIO" />
在6.0之后还需要在使用相机时动态判断是否具备权限:1
2
3
4
5
6private String[] permission = new String[]{
Manifest.permission.CAMERA};
if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(permission, 101);
return;
}
打开相机并实时预览:
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
81HandlerThread handlerThread=new HandlerThread("Camera2");
handlerThread.start();
Handler childHandler=new Handler(handlerThread.getLooper());
handler mainHandler=new Handler(getMainLooper());
private void initCamera() {
CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
return;
}
//打开摄像头
cameraManager.openCamera(CameraCharacteristics.LENS_FACING_BACK + "", stateCallback, mainHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
//相机状态监听回调
private CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {//打开摄像头
mCameraDevice = camera;
//开启预览
takePreview();
}
@Override
public void onDisconnected(CameraDevice camera) {//关闭摄像头
if (null != mCameraDevice) {
mCameraDevice.close();
MainActivity.this.mCameraDevice = null;
}
}
@Override
public void onError(CameraDevice camera, int error) {//发生错误
Toast.makeText(MainActivity.this, "摄像头开启失败", Toast.LENGTH_SHORT).show();
}
};
/**
* 开始预览
*/
private void takePreview() {
try {
// 创建预览需要的CaptureRequest.Builder
final CaptureRequest.Builder previewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
// 将SurfaceView的surface作为CaptureRequest.Builder的目标
previewRequestBuilder.addTarget(surfaceHolder.getSurface());
// 创建CameraCaptureSession,该对象负责管理处理预览请求和拍照请求
mCameraDevice.createCaptureSession(Arrays.asList(surfaceHolder.getSurface(), mImageReader.getSurface()), new CameraCaptureSession.StateCallback() // ③
{
@Override
public void onConfigured(CameraCaptureSession cameraCaptureSession) {
if (null == mCameraDevice) return;
// 当摄像头已经准备好时,开始显示预览
mCameraCaptureSession = cameraCaptureSession;
try {
// 自动对焦
previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 打开闪光灯
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 显示预览
CaptureRequest previewRequest = previewRequestBuilder.build();
mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
@Override
public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
Toast.makeText(MainActivity.this, "配置失败", Toast.LENGTH_SHORT).show();
}
}, childHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}使用相机拍照
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//设置Surface旋转角
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);
}
//imageReader初始化,!!!这个在初始化摄像头时赋值。
mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),
mPreviewSize.getHeight(),
ImageFormat.JPEG,
2 );
mImageReader.setOnImageAvailableListener(onImageAvailableListener , mBackgroundHandler);
public void takePicture(){
try {
CaptureRequest.Builder captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
// 将imageReader的surface作为CaptureRequest.Builder的目标
captureRequestBuilder.addTarget(imageReader.getSurface());
// 自动对焦
captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// 获取手机方向
int rotation = getWindowManager().getDefaultDisplay().getRotation();
// 根据设备方向计算设置照片的方向
captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
//拍照
CaptureRequest mCaptureRequest = captureRequestBuilder.build();
mSession.stopRepeating();
mSession.capture(mCaptureRequest, null, mHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/**监听拍照的图片*/
private ImageReader.OnImageAvailableListener imageAvailableListener= new ImageReader.OnImageAvailableListener()
{
// 当照片数据可用时激发该方法
@Override
public void onImageAvailable(ImageReader reader) {
//先验证手机是否有sdcard
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED)) {
Toast.makeText(getApplicationContext(), "你的sd卡不可用。", Toast.LENGTH_SHORT).show();
return;
}
// 获取捕获的照片数据
Image image = reader.acquireNextImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
//手机拍照都是存到这个路径
String filePath = Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/";
String picturePath = System.currentTimeMillis() + ".jpg";
File file = new File(filePath, picturePath);
try {
//存到本地相册
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(data);
fileOutputStream.close();
//显示图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
iv.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
image.close();
}
}
};使用相机录像
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//设置MediaRecoder属性
private void setUpMediaRecorder() throws IOException {
//设置音频源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置视频源
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//存储路径
if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) {
mNextVideoAbsolutePath = getVideoFilePath(getApplicationContext());
}
mMediaRecorder.setOutputFile(mNextVideoAbsolutePath);
mMediaRecorder.setVideoEncodingBitRate(10000000);
mMediaRecorder.setVideoFrameRate(30);
//视屏宽高
mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight());
//视屏编码格式
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
//音频编码格式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
int rotation = getWindowManager().getDefaultDisplay().getRotation();
switch (mSensorOrientation) {
case SENSOR_ORIENTATION_DEFAULT_DEGREES:
mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
break;
case SENSOR_ORIENTATION_INVERSE_DEGREES:
mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
break;
}
mMediaRecorder.prepare();
}
//视频录制
private void startRecordingVideo() {
if (null == mCameraDevice || !textureView.isAvailable() || null == mPreviewSize) {
return;
}
try {
closePreviewSession();
setUpMediaRecorder();
SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
List<Surface> surfaces = new ArrayList<>();
// Set up Surface for the camera preview
Surface previewSurface = new Surface(texture);
surfaces.add(previewSurface);
mPreviewBuilder.addTarget(previewSurface);
// Set up Surface for the MediaRecorder
Surface recorderSurface = mMediaRecorder.getSurface();
surfaces.add(recorderSurface);
mPreviewBuilder.addTarget(recorderSurface);
// Start a capture session
// Once the session starts, we can update the UI and start recording
mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
mPreviewSession = cameraCaptureSession;
updatePreview();
runOnUiThread(new Runnable() {
@Override
public void run() {
// UI
button.setText("stop");
mIsRecordingVideo = true;
// Start recording
mMediaRecorder.start();
}
});
}
@Override
public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) {
Toast.makeText(getApplicationContext(), "Failed", Toast.LENGTH_SHORT).show();
}
}, mBackgroundHandler);
} catch (CameraAccessException | IOException e) {
e.printStackTrace();
}
}
录像主要时通过TextureView实现实时预览,同时使用MediaRecorder录制视屏。
参考:
[1]. Camera2完全解析 https://www.jianshu.com/p/d83161e77e90
[2]. Camera2介绍 https://blog.csdn.net/vinicolor/article/details/50992692