之前一直用android camera,但现在市面上新出的手机都是支持camera2的,项目要求也是运用camera2,于是自己学习了下,将一些知识点罗列出来,以供复习和大家参考。当然熟悉 opengl的老铁也可以看了这篇后,看看我后面的一篇 android camera2 opengl实时滤镜处理,代码地址
当然代码比较老了,相机preview size 必须手动调整为测试手机支持的size,才可以预览哦。github上的已经解决了这个问题,并会更新,git代码
一、camera2的预览拍照流程
1、获取相机manager,利用manager获得相机ID列表(通过ID选择开启的相机)
private void getCameraIdList () {
CameraManager manager = (CameraManager) mContext.getSystemService(Context
.CAMERA_SERVICE);
try {
mCameraIDList = manager.getCameraIdList();
} catch (CameraAccessException e) {
e.printStackTrace();
}
mCameraID = mCameraIDList[0];
}
上面的函数是将相机的id列表存在mCameraIDlist链表内。
2、通过相机的ID和manager我们就可以获得对应的id的相机的内部参数,如:
mCameraCharacteristics = manager.getCameraCharacteristics(mCameraID);
通过这个mCameraCharacteristics 我们可以获取预览需要的基本参数预览size 和 角度
(1)用来得到相机支持预览的size,从而来设置相机预览的控件大小和拍照图片大小。
//配置参数map
StreamConfigurationMap map = mCameraCharacteristics.get(CameraCharacteristics
.SCALER_STREAM_CONFIGURATION_MAP);
//获取相机支持的size
Size size[] = map.getOutputSizes(ImageFormat.JPEG)
(2)获取当前手相机(surface的角度),下面的方法是根据相机预览角度和相机的预览角度获取当前的倾斜角度,要想预览和成像的图片角度一致,相机角度和窗体角度得一致。
private int getRotateDegree(CameraCharacteristics characteristics) {
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
int displayRotation = wm.getDefaultDisplay().getRotation();
int degrees = 0;
switch (displayRotation) {
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;
}
//获取相机取景的角度(相对于正常竖直角度)(相机确定了,这个值也是固定的(待验证))
int senseOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
return (senseOrientation - degrees + 360) % 360;
}
google的官方文档给出的demo中就利用相机的角度和窗口的角度和大小来选择最合适的预览size,大家可以去参考下。
(3)初始化imageReader,这个类是拍照时候获取图片用的,预览size确定了,我们可以初始化它了。
mImageReader = ImageReader.newInstance(mPreViewSize.getWidth(), mPreViewSize.getHeight(),
ImageFormat.JPEG, 2);
下面则是设置获取图片成功的回调方法:
mImageReader.setOnImageAvailableListener(onImageAvailableListener, null);
当然这里的listener是拍照后,获取图片的时候回调的方法。
3、打开相机
打开相机则是通过前面获取的manager和相机id来打开相应的相机
manager.openCamera(mCameraID, cameraOpenCallBack, null);
对应的方法的原型为
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
相机id 和打开的回调方法必须不为空,后面1个可为空。在相机成功打开后我们才能进行相应的操作。
注意:同一时间只能对同一个相机打开,为了防止我们频繁切换摄像头或者不停的停止和打开相机预览时出现错误,一般都会设定一个相机锁,这里可以自己google,这里不再详述了。
4、相机打开后,创建预览参数类Builder
上面打开相机的回调cameraOpenCallBack中来创建builder,通过builder可以设置我们预览时的各种参数。添加预览的界面(target,预览时候添加textureView的surface,而拍照时候用imageReader的surface),并创建captureSession,并在回调中创建CameraCaptureSession,这个是预览会话,我们最终都是利用这个来控制预览的,因此只有会话配置成功了就可以进行预览了。
private CameraDevice.StateCallback cameraOpenCallBack = new CameraDevice.StateCallback() {
@Override
public void onOpened(CameraDevice camera) {
Log.d(TAG, "相机已经打开");
try {
mCameraDevice = camera;
mPreViewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
SurfaceTexture texture = getSurfaceTexture();//new SurfaceTexture(2);//
texture.setDefaultBufferSize(mPreViewSize.getWidth(), mPreViewSize.getHeight());
mSurface = new Surface(getSurfaceTexture());
mPreViewBuilder.addTarget(mSurface);
mCaptureSession = camera.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface
()), mSessionStateCallBack, null);
} catch (CameraAccessException e) {
Log.d(TAG, "相机创建session失败");
e.printStackTrace();
}
}
@Override
public void onDisconnected(CameraDevice camera) {
}
@Override
public void onError(CameraDevice camera, int error) {
}
};
5 、 会话预览创建后,开启预览
private CameraCaptureSession.StateCallback mSessionStateCallBack = new CameraCaptureSession.StateCallback() {
@Override
public void onConfigured(CameraCaptureSession session) {
Log.d(TAG, "onConfigured......");
mCameraSession = session;
mPreViewBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
rePreView();
cameraLock = false;
}
@Override
public void onConfigureFailed(CameraCaptureSession session) {
}
};
6、给Builder设置参数,并开启预览
其实通过上述基础设置就可以直接调用 mCaptureSession .setRepeatingRequest进行预览了,camera2很强大,我们得好好利用:
(1) 对焦
<1> 自动对焦
mPreViewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
<2> 人脸聚焦
人脸聚焦,其实就是在预览的时候,利用相机预览界面中对人脸的定位,然后重新预览,设置聚焦区域
其实和第三者聚焦方法是相同的,对特定区域聚焦。
private CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
process(result);
}
@Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult) {
super.onCaptureProgressed(session, request, partialResult);
}
private void process(CaptureResult result) {
Face[] camera_faces = result.get(CaptureResult.STATISTICS_FACES);
if (camera_faces != null) {
if (camera_faces.length > 0) {
focusState = FOCUS_FACE_STATE;
faceRect = new Rect(camera_faces[0].getBounds().left, camera_faces[0].getBounds().top, camera_faces[0].getBounds().right, camera_faces[0].getBounds().bottom);
System.out.println("lammy faces" + camera_faces.length );
startPreview(new MeteringRectangle(faceRect, MeteringRectangle.METERING_WEIGHT_MAX));
}
}
}
};
这里startPreview方法中是对制定位置聚焦,其实主要是在重新预览的时候builder设定了参数
mPreViewBuilder.set(CaptureRequest.CONTROL_AF_REGIONS, new MeteringRectangle[]{rect});
mPreViewBuilder.set(CaptureRequest.CONTROL_AE_REGIONS, new MeteringRectangle[]{rect});
<3>手指点击聚焦
其实手指点击聚焦同第二种方式一样,对特定地区进行聚焦,这里也不再详述了。
(2) 闪光灯
闪光灯分2种,一种是手电筒模式,一种是拍照模式,现在分别介绍一下:
(1)手电筒
//关闭模式
mPreViewBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
// 闪一下模式
mPreViewBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
//长亮模式
mPreViewBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
(2)拍照
//自动曝光
mPreViewBuilder.set(CaptureRequest.CONTROL_AE_MODE, CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH);
// 强制曝光
mPreViewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
//不闪光
mPreViewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
闪光灯对部分手机兼容性不是很好,特别是自动曝光,很多手机无效,拍照时候可以利用手电筒打开闪光灯,拍照完毕后会关闭,当然这只适合闪光等开关,不适合自动曝光。
7、拍照
下面是为了兼容各个版本手机,特别摩托罗拉的几款手机,闪光灯先开启或者设置好开预览,然后马上拍照。这样闪光灯可以正常使用,但部分手机的自动曝光失效了,希望知道的老铁们,告诉小弟,如何适配或者解决自动曝光失效的问题:
public void takePhoto(final boolean isStopPreview ,final String savePath)
{
if(mCameraSession == null)
return;
LogUtil.e("takePhoto" , "takePhoto,...............");
this.savePath = savePath;
try {
if(mFlashMode == FLASH_SINGLE) {
mPreViewBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
}
// 闪光灯的几种形式,但不同的手机不一样,通常只能生效的就是长亮 FLASH_MODE_TORCH
// captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
// captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
// captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
// captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE);
// captureBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
// captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
mCameraSession.capture(mPreViewBuilder.build(), new CameraCaptureSession.CaptureCallback() {
@Override
public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
if(isStopPreview) {
isPreview = false;
}
savePhoto(savePath + System.currentTimeMillis()+".jpg");
if(mFlashMode == FLASH_SINGLE) {
closeFlash();
}
}
}, null);
mCameraSession.stopRepeating();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
拍照时候target为imageReader,因此拍照完毕后就会出发imageReader之前注册的listener,然后再里面保存照片等一些操作
private ImageReader.OnImageAvailableListener onImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
try {
Image img = reader.acquireLatestImage();
ByteBuffer buffer = img.getPlanes()[0].getBuffer();
byte[] buff = new byte[buffer.remaining()];
buffer.get(buff);
final Bitmap bitmap = BitmapFactory.decodeByteArray(buff, 0, buff.length);
long t1 = System.currentTimeMillis();
Matrix matrix = new Matrix();
if(mCameraDevice.getId().equals( 0+""))
matrix.postRotate(90);
if(mCameraDevice.getId().equals( 1+""))
matrix.postRotate(-90);
Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
if (takeCaptureCallback != null) {
takeCaptureCallback.takeCaptureSuccess(bitmap2);
}
bitmap.recycle();
final File f = new File(getContext().getExternalFilesDir(null).getAbsolutePath() + "print/" + UUID.randomUUID().toString() + ".jpg");
saveBitmapToSD(bitmap , f.getAbsolutePath());
} catch (Exception e) {
} finally {
if (reader != null) {
reader.close();
}
}
}
};
下面讲下如何获取相机的实时数据,这里是通过imageReader来获取,然后在surfaceView上绘制,大家看下面代码应该就明白了,如果不明白也欢迎大家留言.
private ImageReader.OnImageAvailableListener onPreViewImageAvailableListener = new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
/** 获得相机数据YUV420 */
Image mImage = reader.acquireLatestImage();
if(mImage == null) {
Log.e(TAG, "onImageAvailable .............mImage == null");
return;
}
// Log.e(TAG, "onImageAvailable ............. start");
/** YUV420 转位mat ,然后用opencv转rgb */
Mat mYuvMat = BitmapUtil.imageToMat(mImage);
Mat rgbMat = new Mat(mImage.getHeight(), mImage.getWidth(), CvType.CV_8UC3);
Imgproc.cvtColor(mYuvMat, rgbMat, Imgproc.COLOR_YUV2RGB_I420);
/*************************更新drawBitmap,再绘制,可以达到实时滤镜*********************/
if(drawBitmap == null)
{
drawBitmap = Bitmap.createBitmap(rgbMat.width(), rgbMat.height(), Bitmap.Config.ARGB_8888);
}
if (isTransfer) {
Imgproc.cvtColor(rgbMat, rgbMat, Imgproc.COLOR_RGB2GRAY);
Utils.matToBitmap(rgbMat, drawBitmap);
}else {
Utils.matToBitmap(rgbMat, drawBitmap);
}
/*****************************更新face state***********************/
//在changeCamera的时候切换状态,一面绘图时,最后一帧的时候因为cameraFace 错误,看到切换前一帧 运用到切换后camera的状态
if(mCameraID.equals(mCameraIDList[0])) {
cameraFace = FACE_BACK;
}
else{
cameraFace = FACE_FRONT;
}
//
/*****************************拍照状态,保存图片***********************/
// 连拍的功能,
if(isMultipleTakePhoto && saveNumber < takeNumber){
Log.e(TAG, "onImageAvailable .............take some photos");
savePhoto(savePath + "lammy" + System.currentTimeMillis() + ".jpg");
saveNumber ++;
if(saveNumber >= takeNumber){
if(mCameraSession != null && isStopPreview){
try {
mCameraSession.stopRepeating();
} catch (CameraAccessException e) {
e.printStackTrace();
}
}
isStopPreview = false;
saveNumber = 0;
isMultipleTakePhoto = false;
}
}
// Log.e(TAG, "onImageAvailable .............end");
mImage.close();
if (reader != null) {
// reader.close();
}
}
};