Android Camera开发序列:Camera2 API 的简单描述和调用(预览、拍照、录像)
作者:互联网
Android 5.1 以后,添加了Camera2 的API,能够满足更多操纵camera的场景。当然,相对应camera1的调用,也变的复杂一点。
一、涉及到的关键类
CameraManager -------------- 获取连接的camera情况,执行打开摄像头的操作;
CameraDevice -------------- 当前连接的摄像头对象;
CaptureRequest -------------- camera数据的请求,比如预览、拍照、录像等 ;
CaptureSession -------------- 发送请求后,就建立了一个会话,可以在当前建立的会话上切换各种请求,不需要的时候可以执行关闭;
二、代码实现
下面代码是基于Google提供的demo github.com/googlesampl…
后面自己个人又建了个独立的分支,代码都是基于Google Demo 来的 github.com/yorkZJC/And…
Camera2BaseFragment.java
2.1 这里采用的是TextureView来进行显示,在onResume()的时候,进行判断,如果当前TextureView 可用了,则执行打开摄像头的操作,否则等待TextureView available,第一次打开的是,TextureView还没创建完成,所以会在TextureView available回调中执行打开camera的操作。
@Override public void onResume() { super.onResume(); startBackgroundThread(); if (mTextureView.isAvailable()) { openCamera(mTextureView.getWidth(), mTextureView.getHeight()); } else { mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); } }复制代码
private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { openCamera( width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { } };复制代码
2.2 接下来看下openCamera()的实现
这边完成了camera信息的获取的配置,并调用CameraManager 的openCamera打开摄像头,camera打开状态在CameraDevice.StateCallback中进行回调.
private void openCamera(int width, int height) { if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCameraPermission(); return; } setUpCameraOutputs(width, height); configureTransform(width, height); Activity activity = getActivity(); CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); try { if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { throw new RuntimeException("Time out waiting to lock back camera opening."); } manager.openCamera(mCameraId,mStateCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } catch (InterruptedException e) { throw new RuntimeException("Interrupted while trying to lock camera opening.", e); } }复制代码
2.3 在camera打开的回调中,可以获取到当前的camera对应的CameraDevice,在onOpened()中执行打开预览的操作。
private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice cameraDevice) { // This method is called when the camera is opened. We start camera preview here. mCameraOpenCloseLock.release(); mCameraDevice = cameraDevice; createCameraPreviewSession(); } @Override public void onDisconnected(@NonNull CameraDevice cameraDevice) { mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; } @Override public void onError(@NonNull CameraDevice cameraDevice, int error) { mCameraOpenCloseLock.release(); cameraDevice.close(); mCameraDevice = null; Activity activity = getActivity(); if (null != activity) { activity.finish(); } } };复制代码
2.4 下来就是打开预览的过程,主要做了下面几件事情:
1、预览图像显示在哪里,这就需要绑定surface,这里可以进行多个surface的绑定,如果是上层需要拿到预览数据,则可以设置ImageReader的surface进去;
2、发送预览请求;
3、建立预览会话;
完成这几步,我们就可以看到预览图像了。
private void createCameraPreviewSession() { try { SurfaceTexture texture = mTextureView.getSurfaceTexture(); assert texture != null; // We configure the size of default buffer to be the size of camera preview we want. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // This is the output Surface we need to start preview. Surface surface = new Surface(texture); // We set up a CaptureRequest.Builder with the output Surface. mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewRequestBuilder.addTarget(surface); //request builder可以设置多个target,如果需要拿到实时的预览数据,则把imageReader 的surface 也设进去 // mPreviewRequestBuilder.addTarget(mImageReader.getSurface()); // Here, we create a CameraCaptureSession for camera preview. mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // The camera is already closed if (null == mCameraDevice) { return; } // When the session is ready, we start displaying the preview. mPreviewCaptureSession = cameraCaptureSession; try { // Auto focus should be continuous for camera preview. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // Flash is automatically enabled when necessary. setAutoFlash(mPreviewRequestBuilder); // Finally, we start displaying the camera preview. mPreviewRequest = mPreviewRequestBuilder.build(); // mPreviewCaptureSession.setRepeatingRequest(mPreviewRequest, // mCaptureCallback, mBackgroundHandler); mPreviewCaptureSession.setRepeatingRequest(mPreviewRequest, null, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed( @NonNull CameraCaptureSession cameraCaptureSession) { showToast("Failed"); } }, null ); } catch (CameraAccessException e) { e.printStackTrace(); } }复制代码
2.5 拍照
Camera2 的API,拍照时通过ImageReader返回jpeg数据给上层,交由上层进行保存;
如下面代码所示:
1、首先需要初始化一个JPEG类型的ImageReader,用来接收底层数据回调;
2、设置CameraDevice.TEMPLATE_STILL_CAPTURE 类型的请求,请求拍照;请求成功后,我们需要恢复正常的预览类型请求;
3、在ImageReader回调中将接收到的jpeg数据进行保存;
/** * 初始化一个jpeg类型的imageReader **/ private void initJpegImageReader(int width, int height) { StreamConfigurationMap map = mCameraCharacteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); if (map == null) { return; } Size largest = Collections.max( Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)), new CompareSizesByArea()); mJpegCpatureWidth = largest.getWidth(); mJpegCaptureHeight = largest.getHeight(); mJpegImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2); mJpegImageReader.setOnImageAvailableListener(mJpegImageAvailableListener, mBackgroundHandler); } private final ImageReader.OnImageAvailableListener mJpegImageAvailableListener = new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Log.v(TAG, "--- mJpegImageAvailableListener();reader: " + reader); Image image = reader.acquireLatestImage(); if(image == null){ return; } ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); //将接收到的数据交由独立的线程进行文件的保存操作 mBackgroundHandler.post(new ImageSaver(bytes,mJpegCpatureWidth,mJpegCaptureHeight, generateJpegFile(),mCaptureListener)); image.close(); } }; private void captureStillPicture() { try { if (null == mCameraDevice || mCapturing || mPreviewSession == null) { return; } mCapturing = true; // This is the CaptureRequest.Builder that we use to take a picture. mPreviewBuilder = //设置拍照请求 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); mPreviewBuilder.addTarget(mJpegImageReader.getSurface()); // Use the same AE and AF modes as the preview. // mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, // CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // setAutoFlash(captureBuilder); // Orientation int rotation = 0;//activity.getWindowManager().getDefaultDisplay().getRotation(); mPreviewBuilder.set(CaptureRequest.JPEG_ORIENTATION, 0); CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { //拍照请求成功后,恢复正常的预览模式 startPreview(); mCapturing = false; } }; mPreviewSession.stopRepeating(); mPreviewSession.abortCaptures(); mPreviewSession.capture(mPreviewBuilder.build(),captureCallback , mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }复制代码
2.6 录像
Android API文档(developer.android.google.cn/reference/a…)描述有下面这么一段话,我们可以看到MediaRecorder 的surface也是可以作为target Surface进行数据的请求的。那就很简单了,录像编码需要数据来源,而这个source就是通过MediaRecorder.getsurface,然后把该surface设置为target surface,那么MediaRecorder就可以拿到Camera数据了。
下面看下具体的代码实现:
private void startRecordingVideo() { if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) { return; } try { closePreviewSession(); setUpMediaRecorder(); SurfaceTexture texture = mTextureView.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(); getActivity().runOnUiThread(new Runnable() { @Override public void run() { // UI mButtonVideo.setText(R.string.stop); mIsRecordingVideo = true; // Start recording mMediaRecorder.start(); } }); } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { Activity activity = getActivity(); if (null != activity) { Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); } } }, mBackgroundHandler); } catch (CameraAccessException | IOException e) { e.printStackTrace(); } }复制代码
三、写在后面
看了上面简单的代码流程,我们应该有这样简单的概念。对Camera的操作,无非就是获取到硬件设备相关属性,比如当前挂载了哪些摄像头,摄像头支持哪些分辨率等属性,这个我们需要用到CameraManager来获取;
获取到Camera相关属性后,那么就需要对硬件设备进行操作,操作就是打开Camera,获取预览数据这些了,通过CameraManager,我们能打开对应Id的camera,然后获取到该id对应的Camera设备实例,这个就是CameraDevice了;
那么接下来就是怎么怎么把Camera数据显示到UI上,这时就用到Surface了,我们可以这样理解,Surface是图像显示的介质,Camera2 API 允许我们设置多个Surface为输出目标,比如上面我们说的ImageReader、SurfaceTexutre、MediaRecorder相关的Surface都可以设为目标Surface,底层会帮我们进行数据的填充和显示。这些Surface我们需要预先初始化好参数;
那么有了显示的载体后,就可以进行显示了,Camera2里面就用到了个CaptureRequest 来触发数据的请求,这个request又可以根据自己的使用场景设置不同的请求类型,比如是 预览场景,则可以设置请求类型为 CameraDevice.TEMPLATE_PREVIEW,录像场景下,则设置为CameraDevice.TEMPLATE_RECORD,拍照场景下,则设置为CameraDevice.TEMPLATE_STILL_CAPTURE;
完成了上面这些后,还需要最后一步,就是建立会话了,也就是CaptureSession。我们可以理解为,上面所做的准备,都是为了建立会话,建立了会话后,和Camera之间的交互才真正建立起来。这个会话可以随时关闭,也可以修改参数。
标签:Camera,camera,Camera2,Surface,API,Override,new,CameraDevice,void 来源: https://blog.51cto.com/15152644/2690494