一、Camera2简介

Camera2是Google在Android 5.0后推出的一个全新的相机API,Camera2和Camera没有继承关系,是完全重新设计的,且Camera2支持的功能也更加丰富,但是提供了更丰富的功能的同时也增加了使用的难度。Google的官方Demo:https://github.com/googlesamples/android-Camera2Basic

二、Camera2 VS Camera

以下分别是使用Camera2和Camera打开相机进行预览并获取预览数据的流程图。

 
Camera2 API使用流程
 
Camera API使用流程

可以看到,和Camera相比,Camera2的调用明显复杂得多,但同时也提供了更强大的功能:

  • 支持在非UI线程获取预览数据
  • 可以获取更多的预览帧
  • 对相机的控制更加完备
  • 支持更多格式的预览数据
  • 支持高速连拍

但是具体能否使用还要看设备的厂商有无实现。

三、如何使用Camera2

  • 获取预览数据

一般情况下,大多设备其实只支持ImageFormat.YUV_420_888和ImageFormat.JPEG格式的预览数据,而ImageFormat.JPEG是压缩格式,一般适用于拍照的场景,而不适合直接用于算法检测,因此我们一般取ImageFormat.YUV_420_888作为我们获取预览数据的格式,对于YUV不太了解的同学可以戳这里。

mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(), mPreviewSize.getHeight(),
ImageFormat.YUV_420_888, 2);
mImageReader.setOnImageAvailableListener(
new OnImageAvailableListenerImpl(), mBackgroundHandler);

其中OnImageAvailableListenerImpl的实现如下

  private class OnImageAvailableListenerImpl implements ImageReader.OnImageAvailableListener {
private byte[] y;
private byte[] u;
private byte[] v;
private ReentrantLock lock = new ReentrantLock(); @Override
public void onImageAvailable(ImageReader reader) {
Image image = reader.acquireNextImage();
// Y:U:V == 4:2:2
if (camera2Listener != null && image.getFormat() == ImageFormat.YUV_420_888) {
Image.Plane[] planes = image.getPlanes();
// 加锁确保y、u、v来源于同一个Image
lock.lock();
// 重复使用同一批byte数组,减少gc频率
if (y == null) {
y = new byte[planes[0].getBuffer().limit() - planes[0].getBuffer().position()];
u = new byte[planes[1].getBuffer().limit() - planes[1].getBuffer().position()];
v = new byte[planes[2].getBuffer().limit() - planes[2].getBuffer().position()];
}
if (image.getPlanes()[0].getBuffer().remaining() == y.length) {
planes[0].getBuffer().get(y);
planes[1].getBuffer().get(u);
planes[2].getBuffer().get(v);
camera2Listener.onPreview(y, u, v, mPreviewSize, planes[0].getRowStride());
}
lock.unlock();
}
image.close();
}
}

  

  • 注意事项

1. 图像格式问题
经过在多台设备上测试,明明设置的预览数据格式是ImageFormat.YUV_420_888(4个Y对应一组UV,即平均1个像素占1.5个byte,12位),但是拿到的数据却都是YUV_422格式(2个Y对应一组UV,即平均1个像素占2个byte,16位),且U和V的长度都少了一些(在Oneplus 5和Samsung Tab s3上长度都少了1),也就是:
(u.length == v.length) && (y.length / 2 > u.length) && (y.length / 2 ≈ u.length);
而YUV_420_888数据的Y、U、V关系应该是:
y.length / 4 == u.length == v.length;
且系统API中android.graphics.ImageFormat类的getBitsPerPixel方法可说明上述Y、U、V数据比例不对的问题,内容如下:

 public static int getBitsPerPixel(int format) {
switch (format) {
...
case YUV_420_888:
return 12;
case YUV_422_888:
return 16;
...
}
return -1;
}

以及android.media.ImageUtils类的imageCopy(Image src, Image dst)函数中有这么一段注释说明的确可能会有部分像素丢失:

public static void imageCopy(Image src, Image dst) {
...
for (int row = 0; row < effectivePlaneSize.getHeight(); row++) {
if (row == effectivePlaneSize.getHeight() - 1) {
// Special case for NV21 backed YUV420_888: need handle the last row
// carefully to avoid memory corruption. Check if we have enough bytes to
// copy.
int remainingBytes = srcBuffer.remaining() - srcOffset;
if (srcByteCount > remainingBytes) {
srcByteCount = remainingBytes;
}
}
directByteBufferCopy(srcBuffer, srcOffset, dstBuffer, dstOffset, srcByteCount);
srcOffset += srcRowStride;
dstOffset += dstRowStride;
}
...
}

 

2. 图像宽度不一定为stride(步长)

在有些设备上,回传的图像的rowStride不一定为previewSize.getWidth(),比如在OPPO K3手机上,选择的分辨率为1520x760,但是回传的图像数据的rowStride却是1536,且总数据少了16个像素(Y少了16,U和V分别少了8)。

3. 当心数组越界
上述说到,Camera2设置的预览数据格式是ImageFormat.YUV_420_888时,回传的Y,U,V的关系一般是
(u.length == v.length) && (y.length / 2 > u.length) && (y.length / 2 ≈ u.length);
U和V是有部分缺失的,因此我们在进行数组操作时需要注意越界问题,示例如下:

  /**
* 将Y:U:V == 4:2:2的数据转换为nv21
*
* @param y Y 数据
* @param u U 数据
* @param v V 数据
* @param nv21 生成的nv21,需要预先分配内存
* @param stride 步长
* @param height 图像高度
*/
public static void yuv422ToYuv420sp(byte[] y, byte[] u, byte[] v, byte[] nv21, int stride, int height) {
System.arraycopy(y, 0, nv21, 0, y.length);
// 注意,若length值为 y.length * 3 / 2 会有数组越界的风险,需使用真实数据长度计算
int length = y.length + u.length / 2 + v.length / 2;
int uIndex = 0, vIndex = 0;
for (int i = stride * height; i < length; i += 2) {
nv21[i] = v[vIndex];
nv21[i + 1] = u[uIndex];
vIndex += 2;
uIndex += 2;
}
}

 

4. 避免频繁创建对象
若选择的图像格式是ImageFormat.YUV_420_888,那么相机回传的Image数据包将含3个plane,分别代表Y,U,V,但是一般情况下我们可能需要的是其组合的结果,如NV21、I420等。由于Java的gc会影响性能,在从plane中获取Y、U、V数据和Y、U、V转换为其他数据的过程中,我们需要注意对象的创建频率,我们可以创建一次对象重复使用。不仅是Y,U,V这三个对象,组合的对象,如NV21,也可以用同样的方式处理,但若有将 NV21传出当前线程,用于异步处理的操作,则需要做深拷贝,避免异步处理时引用数据被修改。

四、示例代码

  • 示例代码
    https://github.com/wangshengyang1996/Camera2Demo
  • demo功能
    • 演示Camera2的使用
    • 获取预览帧数据并隔一段时间将原始画面和处理过的画面显示到UI上
    • 将预览的YUV数据转换为NV21,再转换为Bitmap并显示到控件上,同时也将该Bitmap转换为相机预览效果的Bitmap显示到控件上,便于了解原始数据和预览画面的关系
  • 运行效果

     
    效果图

最后,推荐给大家一个比较好用的开源安卓人脸识别sdk:

https://ai.arcsoft.com.cn/ucenter/user/reg?utm_source=cnblogs&utm_medium=referral

Android使用Camera2获取预览数据的更多相关文章

  1. Android Camera2获取预览尺寸和fps范围

    升降摄像头安卓手机刚上市的时候,有些很流行的app刚打开时,前置摄像头就升起来了.好像就是出来看一眼然后又收回去. 虽然我们不调用拍照功能,只是为了获取相机的信息,也是可能让摄像头升起来的. Came ...

  2. Android 使用 Camera2 完成预览和拍照

    Android API 21新增了Camera2,这与之前的camera架构完全不同,使用起来也比较复杂,但是功能变得很强大. 在讲解开启预览之前,首先需要了解camera2的几个比较重要的类: Ca ...

  3. Android平台之不预览获取照相机预览数据帧及精确时间截

    在android平台上要获取预览数据帧是一件极其容易的事儿,但要获取每帧数据对应的时间截并不那么容易,网络上关于这方面的资料也比较少.之所以要获取时间截,是因为某些情况下需要加入精确时间轴才能解决问题 ...

  4. RecyclerView预览数据

    我们在布局文件里定义RecyclerView时,可以使用tools属性预览数据,如下: <android.support.v7.widget.RecyclerView android:layou ...

  5. Android开发中遇到的问题(三)——eclipse创建android项目无法正常预览布局文件

    一.问题描述 今天使用SDK Manager将Android SDK的版本更新到了Android 5.1的版本,eclipse创建android项目时,预览activity_main.xml文件时提示 ...

  6. Android CameraX 打开摄像头预览

    目标很简单,用CameraX打开摄像头预览,实时显示在界面上.看看CameraX有没有Google说的那么好用.先按最简单的来,把预览显示出来. 引入依赖 模块gradle的一些配置,使用的Andro ...

  7. 玩转Android Camera开发(四):预览界面四周暗中间亮,仅仅拍摄矩形区域图片(附完整源代码)

    杂家前文曾写过一篇关于仅仅拍摄特定区域图片的demo.仅仅是比較简陋.在坐标的换算上不是非常严谨,并且没有完毕预览界面四周暗中间亮的效果,深以为憾.今天把这个补齐了. 在上代码之前首先交代下,这里面存 ...

  8. Android内嵌PDF预览

    一.在对应模块的build.gradle文件中加入依赖 dependencies { implementation 'com.github.barteksc:android-pdf-viewer:3. ...

  9. Android多种方式实现相机圆形预览

    效果图如下: 一.为预览控件设置圆角 为控件设置ViewOutlineProvider public RoundTextureView(Context context, AttributeSet at ...

随机推荐

  1. python3 基本使用多线程

    #coding=utf-8 import threading #进口threading from time import sleep import time def task1(): print (& ...

  2. RGB565与RGB555标志识别位图文件格式

    近日从本地16比特位图读出象素彩色数据,并填充ANDROID的BITMAP数据.发现,使用CAVAS当屏幕显示,照片显示的颜色不正确,找了很多资料,原来发现两个原因: 1.将位图的颜色分量掩码弄错了, ...

  3. linq to entity GroupBy多个字段

    var items = _voteRecordRepository.GetAll() .Where(x => programIds.Contains(x.ProgrammeId)) .Group ...

  4. js,css引用顺序设定

    遇到的困难 在ASP .NET MVC里面,会使用_Layout.cshtml来绘制一些全局的公共页面,以及引用相关的css和js而在每个独立的页面中,也有自己独立的js一般来说,希望公共的js放在独 ...

  5. Delphi 接口使用中,对象生命周期管理,如何释放需要注意的问题

    网上有篇文章<Delphi接口编程的两大陷阱>,里面提到接口的生存期管理的问题.但该文章里面提到的两个问题,其实都是对 Delphi 不理解导致的.   先说该篇文章中提到的第一个问题为什 ...

  6. WP8.1使用HttpClient类

    Uri uri = new Uri("http://www.cnsos.net/weburl/index.htm", UriKind.Absolute); HttpClient m ...

  7. 笔记:Advanced Installer 打包Web应用

    原文:笔记:Advanced Installer 打包Web应用 公司要做一款增值税小产品,区别于ACME,本产品核心只有销项部分,面对的客户群是小企业,单税盒单开票机..... 我要做的主要有以下几 ...

  8. oracle 使用db_link 导入导出小结

    客户有一个需求,是将一个库中的某个用户迁移到一台新的oracle服务器上,因数据量较小,并且不涉及版本的升级,所以可以采用创建一个dblink,然后通过这个dblink直接从源库将用户数据导出并导入到 ...

  9. UWP开发-在UWP中使用sqlite

    原文:UWP开发-在UWP中使用sqlite sqlite是一种轻量级的数据库,对于一些资源紧张又需要数据库的开发非常好用. SQLite 是一个开源的无服务器嵌入式数据库. 这些年来,它已作为面向存 ...

  10. 网易Lofter

    作为老网虫,对网易的感情是很深的.当我知道Lofter这个东西后,曾经很兴奋要好好打理自己的博客,然而兴奋很快过了.因为Lofter无时无刻不在刷存在感.无限空间加无限图片流量,国内的服务无法做的更好 ...