android Camera2使用

前言:由于有关camera2使用和对数据处理的比较少所以笔者也有着乐于助人心所以有了后面的内容。咋们废话不多说先把流程和目的说下。首先是获取到相关摄像头id、然后打开摄像、接收摄像头数据回调、将y、u、v拼接成完整的yuv、对数据进行旋转生成正常用户看到的画面,以及对yuv数据编码为h264数据。

Camera2 API介绍

1.获取前或者后置摄像头 摄像头都有对应的摄像头id、获取到摄像头id后面有相应的接口打开此id

CameraManager cameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
for (String cameraId : cameraManager.getCameraIdList()){CameraCharacteristics characteristic= cameraManager.getCameraCharacteristics(cameraId);Integer facing = characteristic.get(CameraCharacteristics.LENS_FACING);if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {Log.d(TAG, "onSurfaceTextureAvailable: front camera is cameraid="+cameraId);break;}}

2.打开摄像头以及打开状态回调

cameraManager.openCamera(mCameraId, mCameraDeviceStateCallback, mHandler);
//回调
public  CameraDevice mCameraDevice;private CameraDevice.StateCallback mCameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(CameraDevice camera) {try {mCameraDevice = camera;//保存此摄像头对象startPreview(camera);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onDisconnected(CameraDevice camera) {camera.close();mCameraDevice = null;}@Overridepublic void onError(CameraDevice camera, int error) {camera.close();mCameraDevice = null;}};

3.设置分辨率 可根据你显示的view大小进行设置、设置回调数据格式、以及添加预览和回调数据监听

 SurfaceTexture texture = mPreviewView.getSurfaceTexture();
//      这里设置的就是预览大小texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());Surface surface = new Surface(texture);//设置读取的图片分辨率CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);CameraCharacteristics characteristics= manager.getCameraCharacteristics(mCameraId);StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);//小米不支持NV21  支持YV12Size largest = Collections.max(Arrays.asList(map.getOutputSizes(ImageFormat.YV12)),//YUV_420_888new CompareSizesByArea());List list =   Arrays.asList(map.getOutputSizes(ImageFormat.YV12));Size size4 = new Size(640,480);if (list.contains(size4) == false){size4 = largest;}/*此处还有很多格式,比如我所用到YUV等 最大的图片数, 此处设置的就是输出分辨率mImageReader里能获取到图片数,但是实际中是2+1张图片,就是多一张*/mImageReader = ImageReader.newInstance(size4.getWidth(), size4.getHeight(), ImageFormat.YV12,2);//监听数据回调mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mHandler);// 这里一定分别add两个surface,一个Textureview的,一个ImageReader的,如果没add,会造成没摄像头预览,或者没有ImageReader的那个回调!!mCaptureRequest.addTarget(surface);mCaptureRequest.addTarget(mImageReader.getSurface());

分析回调数据yuv数据和存储为yuv420p

格式讲解

I420:YYYYYYYY UU VV =>YUV420P
YV12: YYYYYYYY VV UU =>YUV420P
NV12: YYYYYYYY UVUV =>YUV420SP
NV21: YYYYYYYY VUVU =>YUV420SP

数据按y、u、v分开获取与存储、由于之前选择的格式有可能为yuv420_888、NV21、YV12但是经过如下取出后一定是yuv420p

方法一

//取出Y数据ByteBuffer Ybuffer = image.getPlanes()[0].getBuffer();int ysize = Ybuffer.remaining();byte[] yData = new byte[width*height];Ybuffer.get(yData);//拼接两段uv数据byte[] uvData = new  byte[width*height/2];int uSize = width*height/4;int vSize = uSize;//取出planes[1]中的数据  此中的数据一定为UByteBuffer uvBuffer1 = image.getPlanes()[1].getBuffer();int uvsize1 = uvBuffer1.remaining();//此处大小为width*height/2.0  因为步幅为2byte[] uvBuffData1 = new byte[uvsize1];uvBuffer1.get(uvBuffData1);for (int i=0;i<uSize;i++){uvData[i] = uvBuffData1[i*planes[1].getPixelStride()];}//取出planes[2]中的数据 此中的数据一定为VByteBuffer uvBuffer2 = image.getPlanes()[2].getBuffer();int uvsize2 = uvBuffer2.remaining();//此处大小为width*height/2.0  因为步幅为2byte[] uvBuffData2 = new byte[uvsize2];uvBuffer2.get(uvBuffData2);for (int i=0;i<vSize;i++){uvData[uSize+i] = uvBuffData2[i*planes[2].getPixelStride()];}try {saveTofile("yuv420_"+width+"_"+height+".yuv",yData);} catch (IOException e) {e.printStackTrace();}try {saveTofile("yuv420_"+width+"_"+height+".yuv",uvData);} catch (IOException e) {e.printStackTrace();}

方法二

private  byte[] getDataFromImage(Image image, int colorFormat) {if (colorFormat != COLOR_FormatI420 && colorFormat != COLOR_FormatNV21) {throw new IllegalArgumentException("only support COLOR_FormatI420 " + "and COLOR_FormatNV21");}if (!isImageFormatSupported(image)) {throw new RuntimeException("can't convert Image to byte array, format " + image.getFormat());}Rect crop = image.getCropRect();int format = image.getFormat();int width = crop.width();int height = crop.height();Image.Plane[] planes = image.getPlanes();int totaolLength = ImageFormat.getBitsPerPixel(format);//12byte[] data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];//yuv一帧数据byte[] rowData = new byte[planes[0].getRowStride()];//一行数据if (VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");int channelOffset = 0;int outputStride = 1;for (int i = 0; i < planes.length; i++) {switch (i) {case 0:channelOffset = 0;outputStride = 1;break;case 1:if (colorFormat == COLOR_FormatI420) {channelOffset = width * height;outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height + 1;outputStride = 2;}break;case 2:if (colorFormat == COLOR_FormatI420) {channelOffset = (int) (width * height * 1.25);outputStride = 1;} else if (colorFormat == COLOR_FormatNV21) {channelOffset = width * height;outputStride = 2;}break;}ByteBuffer buffer = planes[i].getBuffer();int rowStride = planes[i].getRowStride();//1440int pixelStride = planes[i].getPixelStride();//Y-->1 U/V-->2if (VERBOSE) {Log.v(TAG, "pixelStride " + pixelStride);Log.v(TAG, "rowStride " + rowStride);Log.v(TAG, "width " + width);Log.v(TAG, "height " + height);Log.v(TAG, "buffer size " + buffer.remaining());}int shift = (i == 0) ? 0 : 1;int w = width >> shift;//720int h = height >> shift;//540int position = rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift);//0buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));//buff位置从0开始//一行一行添加像素for (int row = 0; row < h; row++) {int length;if (pixelStride == 1 && outputStride == 1) {//存入Y数据length = w;//一行数据的长度buffer.get(data, channelOffset, length);channelOffset += length;} else {length = (w - 1) * pixelStride + 1;//1439 偶数位都是像素 所以只要到长度的最后一个字节即可buffer.get(rowData, 0, length);//保存这一行数据 取了lenght长后 下次取数据的时候将从lenht位置开始for (int col = 0; col < w; col++) {data[channelOffset] = rowData[col * pixelStride];//也就是说每隔一个字节才是像素  也就是偶数位置的都是像素channelOffset += outputStride;//下一个像素}}if (row < h - 1) {int position2 = buffer.position();//下一次取出数据从那个位置开始buffer.position(buffer.position() + rowStride - length);}}if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);}return data;}

目前看到的效果为默认的横屏数据:

前后摄像头数据旋转

前置摄像头需要旋转顺时针270度 后置摄像头只需旋转90度、旋转算法如下

用户后置摄像头 将yuv420p旋转90度void yuv_rotate_90(byte[] des,byte[] src,int width,int height){int n = 0;int hw = width / 2;//u 应该看成只有Y的1/4的小长方形int hh = height / 2;//copy yfor(int j = 0; j < width;j++){for(int i = height - 1; i >= 0; i--){des[n++] = src[width * i + j];}}//copy ufor(int j = 0;j < hw;j++){for(int i = hh - 1;i >= 0;i--){des[n++] = src[width * height + hw*i + j ];//ptemp[ hw*i + j ];}}//copy vfor(int j = 0; j < hw; j++){for(int i = hh - 1;i >= 0;i--){des[n++] = src[width * height + width * height / 4 + hw*i + j];//ptemp[hw*i + j];}}}//前置摄像头需要逆时针旋转90即 270顺void yuv_rotate_270(byte[] des,byte[] src,int width,int height){int n = 0;int hw = width / 2;int hh = height / 2;//copy yfor(int j = width; j > 0; j--){for(int i = 0; i < height;i++){des[n++] = src[width*i + j];}}//copy ufor(int j = hw-1; j >=0;j--){for(int i = 0; i < hh;i++){des[n++] = src[width * height + hw * i + j]; //ptemp[hw * i + j];}}//copy vfor(int j = hw-1; j >=0;j--){for(int i = 0; i < hh;i++){des[n++] = src[width * height + width * height / 4 +hw * i + j];//ptemp[hw * i + j];}}}//水平镜像void yuv_flip_horizontal(byte[] des,byte[] src,int width,int height){int n = 0;int hw = width / 2;int hh = height / 2;//copy yfor(int j = 0; j < height; j++){for(int i = width - 1;i >= 0;i--){des[n++] = src[width * j + i];}}//copy ufor(int j = 0; j < hh; j++){for(int i = hw - 1;i >= 0;i--){des[n++] = src[width * height + hw * j + i];//ptemp[hw * j + i];}}//copy vfor(int j = 0; j < hh; j++){for(int i = hw - 1;i >= 0;i--){des[n++] = src[width*height + width * height / 4 + hw * j + i];//ptemp[hw * j + i];}}}

旋转后的效果为竖屏数据:

传送门:

源码
下载后的LLACamera有相关代码

目前正在持续更新中有喜欢的小伙伴可以fork一下、顺便给个star谢谢、你的赞赏是我持续的动力。

有疑问的小伙伴欢迎加交流讨论QQ:206931384
有钱的小伙伴们走如下通道:

Android摄像头数据采集与处理相关推荐

  1. android 摄像头参数,获取Android设备上的详细的摄像头信息

    原标题:获取Android设备上的详细的摄像头信息 如何获取Android设备上的详细的摄像头信息呢? 目前Samsung的Galaxy Tab和Nexus S均有前置摄像头,获取Android摄像头 ...

  2. android摄像头的autoFocus-----循环自动聚焦的实现

    参考:http://blog.sina.com.cn/s/blog_7dbac1250101mloj.html 采用重力感应方式已实现 要实现android摄像头的autoFocus,并不难,但要实现 ...

  3. android 摄像头比例,Android摄像头是全屏预览最简单的方式.doc

    Android摄像头是全屏预览最简单的方式 Android Camera做全屏预览之最简单方法 M厂开发五部:刘 博 一.全屏预览与非全屏预览的区别 对于大多数人来说,我们看电影.玩游戏等都喜欢全屏, ...

  4. 2013新春奉送:Android摄像头开发完美demo---(循环聚焦,缩放大小,旋转picture,查询支持的picturesize, ImageButton按键效果)

    [补充:我已在对此代码进行了全面的升级,升级后代码结构更加利于维护扩展,全面适配所有手机,参见博文.此文中的资源也不要再下载了,请下载升级后的代码,如有问题请留言反馈,谢谢.------------- ...

  5. Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView)

    [后注:]下载代码的注意,我的手机是4.3寸的屏,华为U9200.如果不能运行的请修改参数.看前文的第四条.Y的,省的说我传的代码不能用  最近一直在审视以前做过的东西,关于android摄像头预览, ...

  6. Android摄像头开发完美demo---(循环聚焦,缩放大小,旋转picture,查询支持的picturesize, ImageButton按键效果)

    这个代码几乎涉及到了摄像头开发的所有方面,(除了PreviewCallback,这块东西我会结合android摄像头自动识别人脸/火灾来谈),且力求精简,是杂家的心血阿!相对之前改进之处有: 1,精简 ...

  7. Android摄像头 只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理 底层Surface

    [后注:]下载代码的注意,我的手机是4.3寸的屏,华为U9200.如果不能运行的请修改参数.看前文的第四条.Y的,省的说我传的代码不能用  最近一直在审视以前做过的东西,关于android摄像头预览, ...

  8. 2013新春奉送 Android摄像头开发完美demo--- 循环聚焦 缩放大小 旋转picture 查询支持的pict

    [补充:我已在对此代码进行了全面的升级,升级后代码结构更加利于维护扩展,全面适配所有手机,参见博文.此文中的资源也不要再下载了,请下载升级后的代码,如有问题请留言反馈,谢谢.------------- ...

  9. Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整(原理:底层SurfaceView+上层绘制ImageView)...

    Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView) 分类: Android开发 Androi ...

最新文章

  1. 使用了未经检查或不安全的操作_上溪镇开展压痕机使用企业安全生产专项检查...
  2. Oracle在linux下使用小技巧
  3. Redundant Binary Upload. There already exists a bi
  4. android 自定义取色器,【Android自定义View】仿Photoshop取色器ColorPicker(二)
  5. LVM---逻辑盘卷管理
  6. 关于OAuth2.0 Authorization Code+PKCE flow在原生客户端(Native App)下集成的思考
  7. linux家用系统版本,查看linux系统版本
  8. quartus管脚分配后需要保存吗_电脑磁盘显示未分配怎么办?磁盘数据如何恢复?...
  9. mysql5.7的存储过程_MySql5.7命令笔记(三)mysql存储过程命令
  10. 美团面试题:Java-线程池 ThreadPool 专题详解
  11. MTK手机平台充电原理
  12. iOS 逆向编程(二)越狱入门知识
  13. 怎么压缩PPT大小?
  14. 基于epoll,socket与protobuf的简单帧同步游戏服务器
  15. python 批量增加文件前缀_linux中批量添加文件前缀的操作方法
  16. jQuery-5(HTML DOM)
  17. 【Pytorch Lighting】第 8 章:自监督学习
  18. String 去掉空格回车等符号
  19. java解析outlook的msg邮件(outlook-message-parser)
  20. 处理echarts地图省份坐标重叠的方法

热门文章

  1. 安装LNMP环境并跑一个CI框架demo
  2. websphere性能设置和日常维护 (转载)
  3. YOLO的XML和TXT标注文本解释
  4. Python学习笔记#4:快速生成二维矩阵的方法
  5. ITIL( IT Infrastructure Library)
  6. 微信平台中嵌入课程直播的有效方法
  7. ios12.2 打不开 php,苹果关闭iOS12.2验证通道 目前已无法降级
  8. Java学习其五个必经阶段路线图及薪资如何?
  9. 留存分析方法+案例+参考代码
  10. 小区业主入户安检小程序开发