1.Camera2
Android API 21新增了Camera2,与之前的Camera架构完全不同,使用起来也比较复杂,但是功能变得很强大。
Camera2中几个比较重要的类:
①CameraManager: 管理手机上的所有摄像头设备。它其实是一个系统服务,通过getSystemService(Context.CAMERA_SERVICE)获取,它的作用主要是获取摄像头列表和打开指定的摄像头。
通过CameraManager对象可以得到一些相机的基本信息,这个信息就存储在CameraCharacteristic对象中。获取到相机的一些基本信息:
CameraCharacteristics cameraInfo = mCameraManager.getCameraCharacteristics(cameraId);
除了获取Camera的属性信息外,CameraManager对象最重要的作用就是打开相机(openCamera),通过CameraManager才能真正的拿到CameraDevice对象去操作相机:
mCameraManager.openCamera(cameraId, mCameraDeviceStateCallback, null);
cameraId:需要打开的摄像头的id
mCameraDeviceStateCallback:一个CameraDevice的状态回调类,在这个类回调方法里,会告知Camera设备的打开状态,成功还是失败
Handler:openCamera的操作在哪个线程执行,null即为在主线程执行
②CameraDevice:表示一个打开的相机设备,在CameraDevice.StateCallback的onOpened回调里返回此实例对象。它的作用主要是创建CameraCaptureSession和CaptureRequest。
有了CameraDevice对象,就可以创建上层与Camera设备之间的一个会话,在Camera2中即为CameraCaptureSession:
mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface,mImagerReader.getSurface()),mCaptureSessionStateCallback,null);
第一个参数List
第二个参数mCaptureSessionStateCallback:CameraCaptureSession创建的对象的状态回调
第三个参数Handler:createCaptureSession的操作在哪个线程执行,null即为在主线程执行
③CameraCaptureSession: 相机捕获会话,代表上层与底层之间的一个会话,通过这个会话可以下发指令给相机,让相机执行预览、拍照录像等操作,在mCameraDevice.createCaptureSession的回调中获得。主要作用是用于处理拍照和预览的工作(很重要)。
想从相机设备中获取Image,首先要创建一个CameraCaptureSession把接收数据的载体传给相机设备,目前能接收相机数据的载体是Surface和SurfaceTexture。
通过情况下,相机的预览数据可以使用SurfaceView和TextureView接收。拍照使用ImageReader、录视频使用MediaCodec或MediaRecorder。
④CaptureRequest和CaptureResult
CaptureRequest代表相机捕获请求,当CaptureCaptureSession创建好之后,就可以使用这个会话来下发指令,表明当前是需要预览、拍照、还是录像。CaptureRequest对象可以携带设置的参数,比如是否自动对焦、自动曝光、自动白平衡等。CaptureRequest对象的创建使用了建造者模式,需要通过CaptureRequest.Builder来创建,通过调用build方法:
PreviewRequest = mCaptureRequestBuilder.build();
mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动连续对焦
mCaptureRequestBuilder.addTarget(mPreviewSurface); //预览流,底层回来的数据放在哪里
mCaptureRequestBuilder.addTarget(mImageReader.getSurface());拍照流
PreviewRequest = mCaptureRequestBuilder.build();
//这里是下发预览,如果是拍照可以调用capture()方法
mCameraCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureSessionCaptureCallback, null);
CaptureRequest定义了输出缓冲区以及显示界面(TextureView或SurfaceView)等。
CaptureResut代表捕获请求返回的一些结果信息,从里面可以获取一些Metadata数据信息。
2.Camera2实现预览和拍照
使用Camera2进行预览和拍照的主要流程:
①设置TextureView监听
在布局文件中加入TextureView控件作为预览界面,然后实现其监听事件:
textureView.setSurfaceTextureListener(textureListener);
TextureView.SurfaceTextureListener textureListener = new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable( SurfaceTexture surface, int width, int height) {
//当SurefaceTexture可用的时候,设置相机参数并打开相机
setupCamera(width, height);
openCamera();
}
@Override
public void onSurfaceTextureSizeChanged( SurfaceTexture surface, int i, int i1) {
}
@Override
public void onSurfaceTextureDestroyed( SurfaceTexture surface) {
}
@Override
public void onSurfaceTextureUpdated( SurfaceTexture surface) {
}
};
当SurefaceTexture准备好后会回调listener的onSurfaceTextureAvailable()方法,在该方法中设置设置相机参数并打开相机。
②设置相机参数
Camera2中使用CameraManager来管理摄像头。为了更好地预览,根据TextureView的尺寸设置预览尺寸。
private void setupCamera(int width, int height) {
//获取摄像头的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
try {
//遍历所有摄像头
for (String cameraId: manager.getCameraIdList()) {
CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
//默认打开后置摄像头
if (characteristics.get( CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
continue;
//获取StreamConfigurationMap,它是管理摄像头支持的所有输出格式和尺寸
StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
//根据TextureView的尺寸设置预览尺寸
mPreviewSize = getOptimalSize( map.getOutputSizes(SurfaceTexture.class), width, height);
mCameraId = cameraId;
break;
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
/** 选择sizeMap中大于并且最接近width和height的size **/
private Size getOptimalSize(Size[] sizeMap, int width, int height) {
List
for (Size option : sizeMap) {
if (width > height) {
if (option.getWidth() > width && option.getHeight() > height) {
sizeList.add(option);
}
} else {
if (option.getWidth() > height && option.getHeight() > width) {
sizeList.add(option);
}
}
}
if (sizeList.size() > 0) {
return Collections.min(sizeList, new Comparator
@Override
public int compare(Size lhs, Size rhs) {
return Long.signum(lhs.getWidth() * lhs.getHeight() - rhs.getWidth() * rhs.getHeight());
}
});
}
return sizeMap[0];
}
③开启相机
Camera2中打开相机也需要通过CameraManager类:
private void openCamera() {
//获取相机的管理者CameraManager
CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
//检查权限
try {
if (checkSelfPermission( Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
//打开相机,第一个参数表示打开哪个摄像头,第二个参数stateCallback为相机的状态回调接口,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
manager.openCamera(mCameraId, stateCallback, null);
} else {
requestPermissions(new String[]{ Manifest.permission.CAMERA}, 101);
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
实现StateCallback接口,当相机打开后会回调onOpened方法,在该方法里面开启预览:
private final CameraDevice.StateCallback stateCallback = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera){
mCameraDevice = camera;
startPreview(); //开启预览
}
}
④开启相机预览
使用TextureView显示相机预览数据,Camera2的预览和拍照数据都是使用CameraCaptureSession会话来请求的:
private void startPreview() {
setupImageReader(); //用来实现拍照功能
SurfaceTexture mSurfaceTexture = textureView.getSurfaceTexture();
//设置TextureView的缓冲区大小
mSurfaceTexture.setDefaultBufferSize( mPreviewSize.getWidth(), mPreviewSize.getHeight());
//获取Surface显示预览数据
mPreviewSurface = new Surface( mSurfaceTexture);
try {
getPreviewRequestBuilder(); //创建预览请求的builder
//创建相机捕获会话CameraCaptureSession 该对象负责管理处理预览请求和拍照请求。第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
mCameraDevice.createCaptureSession( Arrays.asList(mPreviewSurface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured( CameraCaptureSession session) {
mCaptureSession = session;
repeatPreview();
}
@Override
public void onConfigureFailed( CameraCaptureSession session) {
}
}, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
private void setupImageReader() {
//前三个参数分别是需要的尺寸和格式,最后一个参数代表每次最多获取几帧数据
mImageReader = ImageReader.newInstance( mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.JPEG, 1);
//监听ImageReader的事件,当有图像流数据可用时会回调onImageAvailable方法,它的参数就是预览帧数据,可以对这帧数据进行处理
mImageReader.setOnImageAvailableListener( new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Log.i(TAG, "Image Available!");
Image image = reader.acquireLatestImage(); //注意:一定要调用reader.acquireLatestImage()和close()方法,否则预览画面就会卡住(close在线程里用完之后调用的)
// 开启线程异步保存图片
new Thread(new ImageSaver(image) ).start();
}
}, null);
}
/**创建预览请求的Builder(TEMPLATE_PREVIEW表示预览请求)**/
private void getPreviewRequestBuilder() {
try {
mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
} catch (CameraAccessException e) {
e.printStackTrace();
}
//设置预览的显示界面
mPreviewRequestBuilder.addTarget( mPreviewSurface);
MeteringRectangle[] meteringRectangles = mPreviewRequestBuilder.get(CaptureRequest.CONTROL_AF_REGIONS);
if (meteringRectangles != null && meteringRectangles.length > 0) {
Log.d(TAG, "PreviewRequestBuilder: AF_REGIONS=" + meteringRectangles[0].getRect().toString());
}
mPreviewRequestBuilder.set( CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
mPreviewRequestBuilder.set( CaptureRequest.CONTROL_AF_TRIGGER, CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
}
private void repeatPreview() {
mPreviewRequestBuilder.setTag( TAG_PREVIEW);
mPreviewRequest = mPreviewRequestBuilder.build();
//设置反复捕获数据的请求,这样预览界面就会一直有数据显示
try {
mCaptureSession.setRepeatingRequest( mPreviewRequest, mPreviewCaptureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
⑤实现PreviewCallback
Camera2没有提供Camera中的PreviewCallback,但Camera2中提供了CameraCaptureSession.CaptureCallback用于实现预览帧数据:
private CameraCaptureSession.CaptureCallback mPreviewCaptureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted( CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
}
@Override
public void onCaptureProgressed( CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
}
};
到此为止,就已经实现了预览流的功能。接下来继续看拍照功能的实现。
⑥实现拍照操作
Camera2拍照是通过ImageReader来实现的,首先先做些准备工作,比如设置拍照参数,如方向、尺寸等。
private static final SparseIntArray ORIENTATION = new SparseIntArray();
static {
ORIENTATION.append(Surface.ROTATION_0, 90);
ORIENTATION.append( Surface.ROTATION_90,0);
ORIENTATION.append( Surface.ROTATION_180, 270);
ORIENTATION.append( Surface.ROTATION_270, 180);
}
在上面的setupImageReaer()方法中已经创建了ImageReader对象,并设置了它的监听事件。
注意:一定要调用reader.acquireLatestImage()和close()方法,否则预览画面就会卡住。
最后,创建保存图片的线程:
public static class ImageSaver implements Runnable {
private Image mImage;
public ImageSaver(Image image) {
mImage = image;
}
@Override
public void run() {
ByteBuffer buffer = mImage.getPlanes()[0].getBuffer();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
mImageFile = new File( Environment.getExternalStorageDirectory() + "/DCIM/myPicture.jpg");
FileOutputStream fos = null;
try {
fos = new FileOutputStream(mImageFile);
fos.write(data, 0 ,data.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
mImageFile = null;
mImage.close(); //必须close,否则连续拍会崩溃
if (fos != null) {
try {
fos.close();
fos = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
现在准备工作做好了,还需要响应点击拍照事件,设置点击拍照按钮调用capture()方法,capture()方法即实现拍照:
private void capture() {
try {
//首先创建请求拍照的CaptureRequest
final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
int rotation = getWindowManager().getDe faultDisplay().getRotation(); //获取屏幕方向
mCaptureBuilder.addTarget( mPreviewSurface);
mCaptureBuilder.addTarget( mImageReader.getSurface());
mCaptureBuilder.set( CaptureRequest.JPEG_ORIENTATION, ORIENTATION.get(rotation)); //设置拍照方向
//停止预览
mCaptureSession.stopRepeating();
//开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted( CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
repeatPreview();
}
};
mCaptureSession.capture( mCaptureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
e.printStackTrace();
}
}