Android Camera系列(五):Camera2
Life was like a box of chocolates, you never know what you’re gonna get.
生命就像一盒巧克力,你永远无法知道下一个是什么味道的。
-
Android Camera系列(一):SurfaceView+Camera
-
Android Camera系列(二):TextureView+Camera
-
Android Camera系列(三):GLSurfaceView+Camera
-
Android Camera系列(四):TextureView+OpenGL ES+Camera
-
Android Camera系列(五):Camera2
本系列主要讲述Android开发中Camera的相关操作、预览方式、视频录制等。项目结构简单、代码耦合性低,旨在帮助大家能从中有所收获(方便copy :) ),对于个人来说也是一个总结的好机会。
Android5.0开始Google推荐使用Camera2
替代android.hardware.Camera
,于是便有了这篇文章。这篇文章我们基于Android Camera系列(一):SurfaceView+Camera中定义好的ICameraManager接口来封装Camera2。
一.概述
Camera2 API是Android 5.0(Lollipop)之后引入的新版相机API。与早期的Camera API相比,Camera2提供了更多的功能和对摄像头硬件的更深入的控制。这使得开发者可以实现更复杂、更高级的摄像头功能,如实时预览、拍照、录像、对焦、闪光灯控制等。
camera2对于camera来说是一套全新的API,这就需要我们先了解下Camera2的整体架构,如下图:
Google重新设计Android Camera API 的目的在于大幅提高应用对于 Android 设备上的相机子系统的控制能力,同时重新组织 API,提高其效率和可维护性。
在CaptureRequest中设置不同的Surface用于接收不同的图片数据,最后从不同的Surface中获取到图片数据和包含拍照相关信息的CaptureResult。
Camera2的核心类和开发步骤请看下图:
二. 相关类
1. CameraManager
CameraManager 是一个负责查询和建立相机连接的系统服务,功能如下:
- 获取当前设备中可用的相机列表
getCameraIdList
- 根据摄像头id返回该摄像头的相关信息
getCameraCharacteristics(String cameraId)
- 根据指定的相机 ID 连接相机设备
- 提供将闪光灯设置成手电筒模式的快捷方式
2. CameraDevice
描述系统摄像头,类似于android.hardware.Camera
- 根据指定的参数创建 CameraCaptureSession。
- 根据指定的模板创建 CaptureRequest。
- 关闭相机设备。
- 监听相机设备的状态,例如断开连接、开启成功和开启失败等。
Camera1和CameraDevice很类似,但又不同。Camera 类几乎负责了所有相机的操作,而 CameraDevice 的功能则十分的单一,就是只负责建立相机连接的事务,而更加细化的相机操作则交给了稍后会介绍的 CameraCaptureSession。
3. CameraCaptureSession
当需要拍照、预览等功能时,需要先创建该类的实例,然后通过该实例里的方法进行控制。
一个 CameraDevice 一次只能开启一个 CameraCaptureSession,绝大部分的相机操作都是通过向 CameraCaptureSession 提交一个 Capture 请求实现的,例如拍照、连拍、设置闪光灯模式、触摸对焦、显示预览画面等等。
4. CameraCharacteristics
CameraCharacteristics 是一个只读的相机信息提供者,其内部携带大量的相机信息,包括代表相机朝向的 LENS_FACING
;判断闪光灯是否可用的 FLASH_INFO_AVAILABLE
;获取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES
等等。CameraCharacteristics 有点像 Camera1 的 Camera.CameraInfo
或者 Camera.Parameters
。
5. CaptureRequest
CaptureRequest 是向 CameraCaptureSession 提交 Capture 请求时的信息载体,其内部包括了本次 Capture 的参数配置和接收图像数据的 Surface。CaptureRequest 可以配置的信息非常多,包括图像格式、图像分辨率、传感器控制、闪光灯控制、3A 控制等等,可以说绝大部分的相机参数都是通过 CaptureRequest 配置的。值得注意的是每一个 CaptureRequest 表示一帧画面的操作,这意味着你可以精确控制每一帧的 Capture 操作。
6. CaptureResult
CaptureResult 是每一次 Capture 操作的结果,里面包括了很多状态信息,包括闪光灯状态、对焦状态、时间戳等等。例如你可以在拍照完成的时候,通过 CaptureResult 获取本次拍照时的对焦状态和时间戳。需要注意的是,CaptureResult 并不包含任何图像数据,前面我们在介绍 Surface 的时候说了,图像数据都是从 Surface 获取的。
7. TotalCaptureResult
TotalCaptureResult继承自CaptureResult,功能类似
8. StreamConfigurationMap
获取输出流配置,如:可支持的预览尺寸
9. Image
一个完整的图片缓存,可从该对象中获取YUV、JPEG、RGBA等数据
10. ImageReader
用于从相机打开的通道中读取需要的格式的原始图像数据,可以设置多个ImageReader。
三. 开发步骤
我们按照ICameraManager
定义的接口实现Camera2的API,我们这里只讲关键步骤
1. 打开摄像头
- 获取想要打开的CameraId
- 配置Camera参数,如:预览尺寸、拍照尺寸
- 打开Camera
public void openCamera() {Log.v(TAG, "openCamera");if (mCameraDevice != null) {return;}mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);// 相机IDString cameraId = setUpCameraOutputs(mCameraManager);if (cameraId == null) return;startBackgroundThread(); // 对应 releaseCamera() 方法中的 stopBackgroundThread()mOrientationEventListener.enable();try {mCameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);// 每次切换摄像头计算一次就行,结果缓存到成员变量中initDisplayRotation();initZoomParameter();mFacing = mCameraCharacteristics.get(CameraCharacteristics.LENS_FACING);Logs.i(TAG, "facing:" + mFacing);if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {return;}// 打开摄像头mCameraManager.openCamera(cameraId, mStateCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}
选择想要打开的CameraId
private String setUpCameraOutputs(CameraManager cameraManager) {String cameraId = null;try {// 获取相机ID列表String[] cameraIdList = cameraManager.getCameraIdList();for (String id : cameraIdList) {// 获取相机特征CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);int facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);if (mCameraId == 0 && facing == CameraCharacteristics.LENS_FACING_BACK) {cameraId = id;break;} else if (mCameraId == 1 && facing == CameraCharacteristics.LENS_FACING_FRONT) {cameraId = id;break;}}if (cameraId == null) {onOpenError(CAMERA_ERROR_NO_ID, "Camera id:" + mCameraId + " not found.");return null;}if (!configCameraParams(cameraManager, cameraId)) {onOpenError(CAMERA_ERROR_OPEN, "Config camera error.");return null;}} catch (CameraAccessException e) {onOpenError(CAMERA_ERROR_OPEN, e.getMessage());return null;} catch (NullPointerException e) {onOpenError(CAMERA_ERROR_OPEN, e.getMessage());return null;}return cameraId;
}
配置预览和拍照尺寸
private boolean configCameraParams(CameraManager manager, String cameraId) throws CameraAccessException {CameraCharacteristics characteristics= manager.getCameraCharacteristics(cameraId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);if (map == null) {return false;}Size previewSize = getSuitableSize(new ArrayList<>(Arrays.asList(map.getOutputSizes(SurfaceTexture.class))));Logs.i(TAG, "previewSize: " + previewSize);mPreviewSize = previewSize;mPreviewWidth = mPreviewSize.getWidth();mPreviewHeight = mPreviewSize.getHeight();Size[] supportPictureSizes = map.getOutputSizes(ImageFormat.JPEG);Size pictureSize = Collections.max(Arrays.asList(supportPictureSizes), new CompareSizesByArea());mPictureSize = pictureSize;Logs.i(TAG, "pictureSize: " + pictureSize);mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);return true;
}
2. 开始预览
- 创建预览请求
CaptureRequest
- 创建预览会话
public void startPreview(SurfaceTexture surfaceTexture) {if (previewing || !isOpen()) {return;}previewing = true;surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());mPreviewSurface = new Surface(surfaceTexture);initPreviewRequest();createCommonSession();
}
Camera2设置预览尺寸和Camera1不同,Camera2只需要设置SurfaceTexture的尺寸即可:
SurfaceTexture.setDefaultBufferSize(width, height)
,Camera2会选择匹配Surface的尺寸的数据渲染到上面。但是这也不是说我们可以随便设置SurfaceTexture的大小,我们最好是从Camera2中选择支持的预览尺寸进行设置。
初始化预览请求
private void initPreviewRequest() {if (mPreviewSurface == null) {Log.e(TAG, "initPreviewRequest failed, mPreviewSurface is null");return;}if (!isOpen()) {return;}try {mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);// 设置预览输出的 SurfacemPreviewRequestBuilder.addTarget(mPreviewSurface);// 设置连续自动对焦mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);// 设置自动曝光mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);// 设置自动白平衡mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO);} catch (CameraAccessException e) {e.printStackTrace();}
}
创建Capture会话,我们需要将用到的所有Surface传进去。此后无论你需要预览还是拍照,摄像头都会将数据渲染到对应的Surface上。
private void createCommonSession() {List<Surface> outputs = new ArrayList<>();// preview outputif (mPreviewSurface != null) {Log.d(TAG, "createCommonSession add target mPreviewSurface");outputs.add(mPreviewSurface);}// picture outputSize pictureSize = mPictureSize;if (pictureSize != null) {Log.d(TAG, "createCommonSession add target mPictureImageReader");mPictureImageReader = ImageReader.newInstance(pictureSize.getWidth(), pictureSize.getHeight(), ImageFormat.JPEG, 1);outputs.add(mPictureImageReader.getSurface());}// preview outputif (!mPreviewBufferCallbacks.isEmpty()) {mPreviewImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);mPreviewImageReader.setOnImageAvailableListener(new OnImageAvailableListenerImpl(), mBackgroundHandler);outputs.add(mPreviewImageReader.getSurface());mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());}try {// 一个session中,所有CaptureRequest能够添加的target,必须是outputs的子集,所以在创建session的时候需要都添加进来mCameraDevice.createCaptureSession(outputs, new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCaptureSession = session;startPreview();}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {Log.e(TAG, "ConfigureFailed. session: " + session);previewing = false;}}, mBackgroundHandler); // handle 传入 null 表示使用当前线程的 Looper} catch (CameraAccessException e) {e.printStackTrace();}
}private void startPreview() {Log.v(TAG, "startPreview");if (mCaptureSession == null || mPreviewRequestBuilder == null) {Log.w(TAG, "startPreview: mCaptureSession or mPreviewRequestBuilder is null");return;}try {// 开始预览,即一直发送预览的请求CaptureRequest captureRequest = mPreviewRequestBuilder.build();mCaptureSession.setRepeatingRequest(captureRequest, null, mBackgroundHandler);Logs.i(TAG, "name:" + Thread.currentThread().getName());mUIHandler.post(() -> onPreview(mPreviewWidth, mPreviewHeight));} catch (CameraAccessException e) {e.printStackTrace();}
}
Camera2中很多的操作都不是线性的,都是在回调里获取结果才能进行下一步操作。例如创建capture会话
createCaptureSession
得在onConfigured
回调后才能开始真正的循环预览。
3. 关闭预览
将Capture会话关闭
public void stopPreview() {Log.v(TAG, "stopPreview");if (mCaptureSession == null) {Log.w(TAG, "stopPreview: mCaptureSession is null");return;}try {mCaptureSession.stopRepeating();previewing = false;} catch (CameraAccessException e) {e.printStackTrace();}
}
4. 拍照
- 使用同一个CaptureSession,拍照之前关闭预览,拍照成功后再次开启预览
public void takePicture(PictureBufferCallback pictureBufferCallback) {mPictureBufferCallback = pictureBufferCallback;captureStillPicture(reader -> {Image image = reader.acquireNextImage();ByteBuffer buffer = image.getPlanes()[0].getBuffer();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);image.close();if (mPictureBufferCallback != null) {mPictureBufferCallback.onPictureToken(bytes);}});
}public void captureStillPicture(ImageReader.OnImageAvailableListener onImageAvailableListener) {if (mPictureImageReader == null) {Log.w(TAG, "captureStillPicture failed! mPictureImageReader is null");return;}mPictureImageReader.setOnImageAvailableListener(onImageAvailableListener, mBackgroundHandler);try {// 创建一个用于拍照的RequestCaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);captureBuilder.addTarget(mPictureImageReader.getSurface());captureBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getJpegOrientation(mDeviceOrientation));// 预览如果有放大,拍照的时候也应该保存相同的缩放Rect zoomRect = mPreviewRequestBuilder.get(CaptureRequest.SCALER_CROP_REGION);if (zoomRect != null) {captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);}stopPreview();mCaptureSession.abortCaptures();final long time = System.currentTimeMillis();mCaptureSession.capture(captureBuilder.build(), new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {Log.w(TAG, "onCaptureCompleted, time: " + (System.currentTimeMillis() - time));try {mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);mCaptureSession.capture(mPreviewRequestBuilder.build(), null, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}startPreview();}}, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();}
}
5. 释放摄像头
- 关闭Capture会话
- 关闭CameraDevice
- 关闭拍照ImageReader
- 关闭预览ImageReader
- 关闭Camera线程
public void releaseCamera() {Log.v(TAG, "releaseCamera");stopPreview();if (null != mCaptureSession) {mCaptureSession.close();mCaptureSession = null;}if (mCameraDevice != null) {mCameraDevice.close();mCameraDevice = null;}if (mPictureImageReader != null) {mPictureImageReader.close();mPictureImageReader = null;}if (mPreviewImageReader != null) {mPreviewImageReader.close();mPreviewImageReader = null;}mOrientationEventListener.disable();stopBackgroundThread(); // 对应 openCamera() 方法中的 startBackgroundThread()mUIHandler.post(() -> onClose());}
四. 预览YUV获取
Camera2想要正确获取预览帧数据接下来我们来详细介绍下Camera2获取YUV数据的各种坑。
上面的代码我们知道,在预览环节中我们设置了预览所需的ImageReader
,并且我们指定了数据采集格式为ImageFormat.YUV_420_888
mPreviewImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 2);
mPreviewImageReader.setOnImageAvailableListener(new OnImageAvailableListenerImpl(), mBackgroundHandler);
outputs.add(mPreviewImageReader.getSurface());
mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
通常获取YUV预览数据方式
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {private byte[] y;private byte[] u;private byte[] v;private byte[] yuvData;private ReentrantLock lock = new ReentrantLock();@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireNextImage();// Y:U:V == 4:2:2if (image.getFormat() == ImageFormat.YUV_420_888) {Image.Plane[] planes = image.getPlanes();int width = image.getWidth();int height = image.getHeight();// 加锁确保y、u、v来源于同一个Imagelock.lock();/** Y */ByteBuffer bufferY = planes[0].getBuffer();/** U(Cb) */ByteBuffer bufferU = planes[1].getBuffer();/** V(Cr) */ByteBuffer bufferV = planes[2].getBuffer();// 重复使用同一批byte数组,减少gc频率if (y == null) {y = new byte[bufferY.limit() - bufferY.position()];u = new byte[bufferU.limit() - bufferU.position()];v = new byte[bufferV.limit() - bufferV.position()];}if (yuvData == null) {yuvData = new byte[width * height * 3 / 2];}int ySize = width * height;YUVFormat yuvFormat = YUVFormat.I420;if (bufferY.remaining() == y.length) {bufferY.get(y);bufferU.get(u);bufferV.get(v);}System.arraycopy(y, 0, yuvData, 0, y.length);System.arraycopy(u, 0, yuvData, ySize, u.length);System.arraycopy(v, 0, yuvData, ySize + u.length, v.length);lock.unlock();}image.close();}}
正常情况下我们通过
image.getPlanes()
获取三个平面的数据分别是Y、U、V,然后组装起来即可。但是实际情况恰恰没有这么简单,接下来我们看看坑的地方。
1. YUV_420_888
YUV_420_888他是YCbCr的泛化格式,不会具体指明是YU12,YV12,NV12或是NV21。他能够表示任何4:2:0的平面和半平面格式,每个分量用8bits表示。
带有这种格式的图像使用3个独立的Buffer表示,每一个Buffer表示一个颜色平面(Plane)。除了Buffer外,他还提供了rowStride
、pixelStride
来描述对应的Plane。这两个参数是获取YUV数据坑的根源,下面回来详细介绍。
使用Image.getPlanes()
获取plane数组:Image.Plane[] planes = image.getPlanes();
它保证planes[0]总是Y,planes[1]总是 U(Cb),planes[2]总是 V(Cr)。并保证Y-Plane永远不会和U/V交叉。U/V-Plane总是有相同的rowStride和pixelStride。
1.1 rowStride和pixelStride
pixelStride
通过getPixelStride()
获取,像素步长,取值1或2。他代表的是行内连续两个颜色之间的距离(步长)。
如果是1,那么每一行中的同一个颜色分量,比如Y分量是连续的。也就是行内索引0,1,2…的颜色分量都是他的。
如果是2,那么每一行中的同一个颜色分量,是不连续。中间会间隔一个元素,也就是行内索引为0,2,4,6…的颜色分量才是他的。
还有个重要的点:假如步长为2,意味索引间隔的颜色才是有效的元素,中间间隔的元素其实是没有意义的。而Android中确实也是这么做的,比如某个plane[1](U分量)步长是2,那么数组下标0,2,4,6…的数据是U分量的,而中间的间隔元素Android会补上V分量,也就是会是UVUVUV…这样去分布。但是当最后一个U分量出现后,最后一个没有意义的元素Android就不补了,也就是最后的V分量就不会补了,直接结束,即是这样分布:UVUVUV…UVUVU。
rowStride
通过getRowStride()
获取,每行数据的宽度,这个和分辨率不是一回事,他是每一行实际存储的空间宽度
1.2 其他参数
width和height
通过getWidth()
和getHeight()
获取,和预览数据分辨率一致
buffer size
这个主要就是plane数组的大小,一般就通过planes[i].length获取
1.3 YUV数据分布和排列
了解了rowStride和pixelStride两个参数后,我们可以来分析下实际场景中遇到的情况了。
(1)Planar格式(P)
我们先看下6*4的图片:
plane[0] 的pixelStride是1,说明没有间隔,Y是连续的;rowStride是6,也就是每行是6个;length=24,24 / 6 = 4,共4行;
plane[1] 的pixelStride也是1,说明没有间隔,U是连续的;rowStride是3,也就是每行是3个;length=6,6 / 3 = 2,共2行,符合YUV420的情况,横纵2:1采样;
plane[2] 和plane[1]相同,V是连续的
上述其实就是YUV420P的标准格式I420,我们期望的解析方式,直接可以取到Y、U、V三个分量。可惜的是,大多数手机不是这样的格式,而是下面要介绍的SP的情况。
(2)Semi-Planar格式(SP)
还是6*4的图片:
plane[0] 的pixelStride是1,说明没有间隔,Y是连续的;rowStride是6,也就是每行是6个;length=24,24 / 6 = 4,共4行;这个Y分量跟Planar格式是一样的。
plane[1] 的pixelStride是2,说明有间隔,U是间隔采样的;我们上面分析过,当pixelStride=2的时候,在U分量中就会间隔插入V分量,因此每一行由本来是Y的一半也就是3,变成了6(也就是rowStride的值),同时会放弃掉最后一个无意义的V分量,所以length=6*2-1=11,行数还是2,纵向不变;
plane[2] 和plane[1]相同
其实我们从上图可以看到,在plane[1]中已经包含了U和V分量了,只不过差了最后一个V分量;对于整张图片来说,少了一个V分量是不影响显示效果的;因此我们可以拿plane[1]的数据,就可以拿到U和V了;plane[2]同理其实也有V和U,这样我们就可以plane[0]+plane[1]组成NV12格式,plane[0]+plane[2]组成NV21格式。
(3)特殊情况
rowStride除了有P和SP格式导致不同以外,他其实还有一个重要的作用。就是在一些特殊的sensor采集的时候,因为芯片处理器要字节对齐取数据等原因导致补齐操作,从而使得每一行所占用的空间比实际数据要多。
继续上图:
plane[0] 的pixelStride是1,说明没有间隔,Y是连续的;rowStride本来应该是6,但是变成了8,最后补了两个空字节,也就是每行是8个;length=32,32 / 8 = 4,共4行;这里我们就需要判断getWidth()
和getRowStride()
是否匹配了,如果不匹配我们就得按行来获取Y分量了。同理**plane[1]和plane[2]**也得按行获取对应的分量了。
2. 代码实现
根据以上的理论,我们要把所有的情况都考虑到,并且我们增加了YUVFormat
区分是P还是SP
private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {private byte[] y;private byte[] u;private byte[] v;private byte[] yuvData;private ReentrantLock lock = new ReentrantLock();@Overridepublic void onImageAvailable(ImageReader reader) {Image image = reader.acquireNextImage();// Y:U:V == 4:2:2if (image.getFormat() == ImageFormat.YUV_420_888) {Image.Plane[] planes = image.getPlanes();int width = image.getWidth();int height = image.getHeight();// 加锁确保y、u、v来源于同一个Imagelock.lock();/** Y */ByteBuffer bufferY = planes[0].getBuffer();/** U(Cb) */ByteBuffer bufferU = planes[1].getBuffer();/** V(Cr) */ByteBuffer bufferV = planes[2].getBuffer();// 重复使用同一批byte数组,减少gc频率if (y == null) {y = new byte[bufferY.limit() - bufferY.position()];u = new byte[bufferU.limit() - bufferU.position()];v = new byte[bufferV.limit() - bufferV.position()];}if (yuvData == null) {yuvData = new byte[width * height * 3 / 2];}YUVFormat yuvFormat = YUVFormat.I420;if (bufferY.remaining() == y.length) {bufferY.get(y);bufferU.get(u);bufferV.get(v);// 数据前期处理// 处理yint yRowStride = planes[0].getRowStride();if (yRowStride == width) {System.arraycopy(y, 0, yuvData, 0, y.length);} else {// 按行提取for (int i = 0; i < height; i++) {System.arraycopy(y, i * yRowStride, yuvData, i * width, width);}}int ySize = width * height;// 判断是p还是spif (planes[1].getPixelStride() == 1) { // PyuvFormat = YUVFormat.I420;int offset = ySize;// 处理Uint uRowStride = planes[1].getRowStride();if (uRowStride == width / 2) {System.arraycopy(u, 0, yuvData, offset, u.length);} else {int rowStride = width / 2;for (int i = 0; i < height / 2; i++) {System.arraycopy(u, i * uRowStride, yuvData, offset + i * rowStride, rowStride);}}offset = ySize + width * height / 4;// 处理Vint vRowStride = planes[2].getRowStride();if (vRowStride == width / 2) {System.arraycopy(v, 0, yuvData, offset, v.length);} else {int rowStride = width / 2;for (int i = 0; i < height / 2; i++) {System.arraycopy(v, i * vRowStride, yuvData, offset + i * rowStride, rowStride);}}} else if (planes[1].getPixelStride() == 2) { // SPyuvFormat = YUVFormat.NV21;int offset = width * height;int uvSize = ySize / 2;// 处理UVint uvRowStride = planes[2].getRowStride();if (uvRowStride == width) {System.arraycopy(v, 0, yuvData, offset, v.length > uvSize ? uvSize : v.length);} else {// 按行提取int rowSize = height / 2;for (int i = 0; i < rowSize; i++) {if (i == rowSize - 1) {int lastLineSize = v.length - i * uvRowStride;System.arraycopy(v, i * uvRowStride, yuvData, offset + i * width, lastLineSize < width ? lastLineSize : width);} else {System.arraycopy(v, i * uvRowStride, yuvData, offset + i * width, width);}}}}}if (!mPreviewBufferCallbacks.isEmpty()) {for (PreviewBufferCallback previewBufferCallback : mPreviewBufferCallbacks) {previewBufferCallback.onPreviewBufferFrame(yuvData, image.getWidth(), image.getHeight(), yuvFormat);}}lock.unlock();}image.close();}}
最后
该篇文章我们讲述了Camera2的基本用法,当然Camera2还有很多高级的用法需要读者自行去挖掘了。前面的几篇文章我们都有对应的预览视图配合Camera使用,这里我们按照ICameraManager
实现Camera2后,Camera1的预览的视图SurfaceView
、TextureView
、GLSurfaceView
、SurfaceView+OpenGL ES
、TextureView+OpenGL ES
都可以无缝迁移使用Camera2Manager预览。
lib-camera库包结构如下:
包 | 说明 |
---|---|
camera | camera相关操作功能包,包括Camera和Camera2。以及各种预览视图 |
encoder | MediaCdoec录制视频相关,包括对ByteBuffer和Surface的录制 |
gles | opengles操作相关 |
permission | 权限相关 |
util | 工具类 |
每个包都可独立使用做到最低的耦合,方便白嫖
github地址:https://github.com/xiaozhi003/AndroidCamera,https://gitee.com/xiaozhi003/android-camera
参考:
- https://github.com/afei-cn/CameraDemo
- https://github.com/saki4510t/UVCCamera
- https://github.com/google/grafika
- http://www.360doc.com/content/23/1205/11/474846_1106367495.shtml