最近刚入职了一家公司,这家公司是做人眼虹膜识别的,所以用到的主要就是Camera相关的知识,公司之前的产品都是基于Camera1,公司就派我去了解研究Camera2。这里我就写下这10天左右我对Camera一窍不通到现在的认识与了解吧。
(注:本文不是从底层讲起,讲Camera2.0的架构之类的,只是讲讲我所掌握了的实用技巧,因为我学习过程中我的很多需求百度不到,写在次一是加深自己印象,而是献给有需要的人)
首先Camera2.0(android.hardware.Camera2)是Android 5.0(API Level 21)推出来用以取代在此版本后过时了Camera(android.hardware.Camera)。Camera2从底层架构就已经与原Camera已经大不相同了。具体的架构和那些官方所述的好处大家可自行百度,网上这样的博客很多。我接下直接说下主要用到的几个类吧。
主要用到的5个类:
它们之间的关系大概就是:首先通过getSystemService(Context.CAMERA_SERVICE)拿到CameraManger,然后通过CameraManger拿到某个摄像头的CameraCharacteristics,得到支持的参数啊CameraCharacteristics以及预览尺寸等信息。然后再通过CameraManger的openCamera拿到一个具体的摄像头对象CameraDevice。该方法有一个参数会要求传递一个回调,当摄像头打开时会该方法:onOpen,然后再在该方法了开启摄像头预览,绑定Surface。大概就这样,直接看Demo吧。
权限
<uses-permission android:name="android.permission.CAMERA"/>布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextureView android:id="@+id/textureview" android:layout_width="fill_parent" android:layout_height="fill_parent"/> </LinearLayout>核心代码
public class Camera2Demo extends Activity implements TextureView.SurfaceTextureListener { private TextureView mPreviewView; private Handler mHandler; private HandlerThread mThreadHandler; private Size mPreviewSize; private CaptureRequest.Builder mPreviewBuilder; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.camera); initView(); initLooper(); } //很多过程都变成了异步的了,所以这里需要一个子线程的looper private void initLooper() { mThreadHandler = new HandlerThread("CAMERA2"); mThreadHandler.start(); mHandler = new Handler(mThreadHandler.getLooper()); } //可以通过TextureView或者SurfaceView private void initView() { mPreviewView = (TextureView) v.findViewById(R.id.textureview); mPreviewView.setSurfaceTextureListener(this); } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { try { //获得所有摄像头的管理者CameraManager CameraManager cameraManager = (CameraManager) getActivity().getSystemService(Context.CAMERA_SERVICE); //获得某个摄像头的特征,支持的参数 CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics("0"); //支持的STREAM CONFIGURATION StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); //摄像头支持的预览Size数组 mPreviewSize = map.getOutputSizes(SurfaceTexture.class)[0]; //打开相机 cameraManager.openCamera("0", mCameraDeviceStateCallback, mHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } // 这个方法要注意一下,因为每有一帧画面,都会回调一次此方法 @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { try { startPreview(camera); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onDisconnected(CameraDevice camera) { } @Override public void onError(CameraDevice camera, int error) { } }; // 开始预览,主要是camera.createCaptureSession这段代码很重要,创建会话 private void startPreview(CameraDevice camera) throws CameraAccessException { SurfaceTexture texture = mPreviewView.getSurfaceTexture(); // 这里设置的就是预览大小 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());</span> Surface surface = new Surface(texture); try { // 设置捕获请求为预览,这里还有拍照啊,录像等 mPreviewBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); } catch (CameraAccessException e) { e.printStackTrace(); } // 就是在这里,通过这个set(key,value)方法,设置曝光啊,自动聚焦等参数!! 如下举例: // mPreviewBuilder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); mImageReader = ImageReader.newInstance(mSurfaceView.getWidth(), mSurfaceView.getHeight(), ImageFormat.JPEG/*此处还有很多格式,比如我所用到YUV等*/, 2/*最大的图片数,mImageReader里能获取到图片数,但是实际中是2+1张图片,就是多一张*/); mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler); // 这里一定分别add两个surface,一个Textureview的,一个ImageReader的,如果没add,会造成没摄像头预览,或者没有ImageReader的那个回调!! mPreviewBuilder.addTarget(surface); mPreviewBuilder.addTarget(mImageReader.getSurface()); mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()),mSessionStateCallback, mHandler); } private CameraCaptureSession.StateCallback mSessionStateCallback = new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession session) { try { updatePreview(session); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession session) { } }; private void updatePreview(CameraCaptureSession session) throws CameraAccessException { session.setRepeatingRequest(mPreviewBuilder.build(), null, mHandler); } private ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() { /** * 当有一张图片可用时会回调此方法,但有一点一定要注意: * 一定要调用 reader.acquireNextImage()和close()方法,否则画面就会卡住!!!!!我被这个坑坑了好久!!! * 很多人可能写Demo就在这里打一个Log,结果卡住了,或者方法不能一直被回调。 **/ @Override public void onImageAvailable(ImageReader reader) { Image img = reader.acquireNextImage(); /** * 因为Camera2并没有Camera1的Priview回调!!!所以该怎么能到预览图像的byte[]呢?就是在这里了!!!我找了好久的办法!!! **/ byte[] data = new byte[reader.remain()]; img.close(); } }; }
怎么用Camera预览拍照,网上的Demo和博客很多,但是Camera2是不存在Camera1中的Preview回调的,也就是要拿到原始预览图像的baye数组或者图像图片,就像上文中红色标注的 onImageAvailable方法里那样做,而且有一些比较容易被坑的地方我也提醒了,让看到人少走点弯路啊。